Compare commits

..

284 Commits

Author SHA1 Message Date
hoteas b680112fdb 修正bug 2022-10-20 15:49:49 +08:00
hoteas 4ab5ab9ff4 时间由指针改回对象 2022-10-20 09:08:23 +08:00
hoteas b425024359 升级流程引擎配置文件版本 2022-10-20 09:00:44 +08:00
hoteas 78337c14ec 升级流程引擎配置文件版本 2022-10-19 21:32:34 +08:00
hoteas cdba8af129 升级流程引擎配置文件版本 2022-10-19 21:29:38 +08:00
hoteas 9a7426180d 增加单表某字段权限及类型控制功能 2022-10-19 03:37:52 +08:00
hoteas b7131603c4 增加备注功能 2022-10-08 17:32:37 +08:00
hoteas 2f3a5a0a59 增加壁纸及壁纸地址缓存 2022-10-08 16:52:42 +08:00
hoteas c2468a7389 增加两个参数 2022-09-16 07:10:45 +08:00
hoteas b6ba3486d6 IP获取真实条件修改 2022-09-02 00:04:23 +08:00
hoteas f59e909d30 修复下载插件bug 2022-08-31 19:15:47 +08:00
hoteas 3bb47de45a 修复下载插件bug 2022-08-31 19:10:41 +08:00
hoteas c71973850e 修复下载插件bug 2022-08-31 19:10:08 +08:00
hoteas 65f062b860 数据库操作bug 2022-08-31 11:20:58 +08:00
hoteas 7b502fed36 数据库操作bug 2022-08-31 11:14:45 +08:00
hoteas fd6b15bdaf 修正查询bug 2022-08-30 06:21:46 +08:00
hoteas f31bf24ec2 增加mongodb驱动,简单版本 2022-08-30 04:44:37 +08:00
hoteas 30edb4d9fa 增加mongodb驱动,简单版本 2022-08-30 04:05:17 +08:00
hoteas a32c3a1589 增加mongodb驱动,简单版本 2022-08-30 03:30:27 +08:00
hoteas 127e027940 修复重复执行context.View()的bug 2022-08-25 14:06:31 +08:00
hoteas 2ee0838d8a 树状结构优化 2022-08-25 13:06:26 +08:00
hoteas 72c23499f2 树状结构优化 2022-08-25 13:02:44 +08:00
hoteas 96d8868694 数据库错误操作优化 2022-08-25 05:01:50 +08:00
hoteas 1533a57cb8 数据库错误操作优化 2022-08-25 03:48:09 +08:00
hoteas 615f52d2e4 数据库错误操作优化 2022-08-25 03:44:58 +08:00
hoteas f7dfd4ec77 增加记录IP 2022-08-21 03:49:56 +08:00
hoteas 2276aa12ef Merge remote-tracking branch 'origin/master' 2022-08-20 22:38:40 +08:00
hoteas 6505594fd0 增加记录IP 2022-08-20 22:38:15 +08:00
hoteas c55e26021b 数据库错误操作优化 2022-08-20 11:20:03 +08:00
hoteas 78420a692f 检索升级 2022-08-19 17:58:18 +08:00
hoteas 6d9af149ad 数据库错误操作优化 2022-08-18 13:40:02 +08:00
hoteas 253cad35ef 数据库错误操作优化 2022-08-18 13:28:02 +08:00
hoteas 47c8736f34 数据库错误操作优化 2022-08-18 12:33:01 +08:00
hoteas dcce8ace9e 数据库事务操作优化 2022-08-18 11:36:23 +08:00
hoteas 125ccc5c3b 数据库操作优化 2022-08-17 17:10:12 +08:00
hoteas 432d59d483 数据库操作优化 2022-08-17 16:42:46 +08:00
hoteas 60f222b011 数据库操作优化 2022-08-17 16:35:52 +08:00
hoteas 7a8b86ed12 数据库操作优化,优化系统管理后台代码生成逻辑 2022-08-17 16:02:36 +08:00
hoteas b940afa6b0 数据库操作优化 2022-08-15 05:59:06 +08:00
hoteas 8afd00ff86 修复登录bug 2022-08-11 10:41:35 +08:00
hoteas 8992fd6d3a 解决BUG 2022-08-11 10:18:48 +08:00
hoteas 524a892480 修复bug 2022-08-08 04:34:26 +08:00
hoteas c420e23edb 修复bug 2022-08-08 02:37:15 +08:00
hoteas 7419e03d67 解决bug 2022-08-04 12:14:18 +08:00
hoteas 9425544d1c 修复bug 2022-08-04 02:41:41 +08:00
hoteas a8d78832df 修复配置bug 2022-08-03 12:07:55 +08:00
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
hoteas 9c3ba81135 测试 2021-09-12 05:35:14 +08:00
hoteas 62cf625fe9 增加表关联新建功能,同时修复数据库bug 2021-08-30 07:36:17 +08:00
hoteas e1f4876621 增加表关联新建功能,同时修复数据库bug 2021-08-29 06:08:19 +08:00
hoteas b480659a22 增加表关联新建功能,同时修复数据库bug 2021-08-28 13:06:00 +08:00
hoteas 09b1d37ae3 增加表关联新建功能,同时修复数据库bug 2021-08-28 13:05:12 +08:00
hoteas 47b28e7b57 开始新增代码生成配置2.0功能,配置文件格式规定完成 2021-07-21 00:55:59 +08:00
hoteas 9cd7ad34b3 修复浏览器cookie不识别 2021-07-21 00:09:13 +08:00
hoteas d736a74198 增加表关联新建功能,同时修复数据库bug 2021-07-14 00:48:34 +08:00
hoteas ada4a926c0 增加表关联新建功能,同时修复数据库bug 2021-07-07 04:08:40 +08:00
hoteas 4ad401bafc 增加上传接口 2021-07-05 02:20:10 +08:00
hoteas 05c8159601 增加上传接口 2021-07-04 04:03:29 +08:00
hoteas efb6cb980e 优化 2021-06-18 04:56:22 +08:00
hoteas 00ad6487b6 加入树节点功能 2021-06-16 06:10:57 +08:00
hoteas 9aa836a72c 加入树节点功能 2021-06-14 01:19:05 +08:00
hoteas 9ea6082d04 接入搜索功能 2021-06-13 10:40:19 +08:00
hoteas 9e595ab6e8 接入搜索功能 2021-06-13 09:53:37 +08:00
hoteas 8b6dd5fdad 接入搜索功能 2021-06-13 03:08:43 +08:00
hoteas 5c8df885a2 优化表单生成规则 2021-06-12 13:33:35 +08:00
hoteas cc4d91cdbb 优化表单生成规则 2021-06-11 09:41:23 +08:00
hoteas 32167d4721 增加menu自动化添加功能 2021-06-11 08:06:44 +08:00
hoteas 1077f09755 优化代码 2021-06-07 11:32:47 +08:00
hoteas 642bf97272 优化代码 2021-06-06 07:50:58 +08:00
hoteas 4038598470 优化代码 2021-06-06 02:27:22 +08:00
hoteas 79453d4d2a 生成代码,逐步进行优化 2021-06-05 02:18:56 +08:00
hoteas 6aa2b74840 生成代码,逐步进行优化 2021-06-04 10:05:07 +08:00
hoteas a003612810 生成代码,逐步进行优化 2021-06-04 10:04:37 +08:00
hoteas d6c11a5d93 已经接入配置文件,开始准备生成代码 2021-06-04 03:03:48 +08:00
hoteas fe085ce889 已经接入配置文件,开始准备生成代码 2021-06-04 02:57:56 +08:00
hoteas 99933e02f4 引入数据库,代码,配置文件三位一体内容,准备进行一体化配置 2021-06-03 05:31:09 +08:00
hoteas 9af232840c 接入RESTfull风格接口 2021-05-30 00:10:07 +08:00
hoteas 465ea889a2 接入RESTfull风格接口 2021-05-30 00:01:15 +08:00
hoteas c46fdd8985 接入RESTfull风格接口 2021-05-29 02:57:03 +08:00
hoteas 9fd0a5fd61 完善error库 2021-05-29 00:54:27 +08:00
hoteas 95489fcf49 完善error库 2021-05-29 00:41:17 +08:00
hoteas 0e2932ca89 完善error库 2021-05-29 00:37:20 +08:00
hoteas 764d88894e 完善error库 2021-05-29 00:15:13 +08:00
hoteas 339302956a 缓存测试成功 2021-05-28 23:48:33 +08:00
hoteas 1101937028 缓存驱动更换成功,支持3级缓存,memory,以及db 2021-05-28 22:52:22 +08:00
hoteas fc315064e7 optimize log tool 2021-05-25 20:27:24 +08:00
hoteas 740059075a optimize log tool 2021-05-25 19:53:34 +08:00
hoteas 770f0a94c9 optimize log tool 2021-05-25 05:28:56 +08:00
hoteas df07afb744 use logrus to project default log tools 2021-05-25 05:08:17 +08:00
hoteas 9bc930750d 整个包结构重构 2021-05-24 07:27:41 +08:00
hoteas 77ded19742 move redis code to package hotime 2021-05-24 06:16:32 +08:00
hoteas 2632517fab mysql add master and slave mode 2021-05-24 06:14:58 +08:00
hoteas c8e926446e add cache to framework 2021-05-24 05:47:56 +08:00
hoteas 253f836571 调整配置项设置 2021-05-23 03:35:49 +08:00
hoteas babf6595ee 测试demo调整 2021-05-22 04:52:03 +08:00
hoteas 91dc207a78 增加readme文件和授权文件 2021-04-30 00:27:12 +08:00
hoteas d334856c6c 升级同时支持http与https 2020-07-17 01:10:28 +08:00
hoteas 51b1eb3149 删除go mod防止影响 2020-07-04 02:27:52 +08:00
hoteas 91d9a069ac 删除go mod防止影响 2020-07-04 02:24:39 +08:00
hoteas 2a4bf676c9 修正gomod 2020-06-15 14:51:55 +08:00
hoteas 0723555b2d 修正gomod 2020-06-15 14:51:06 +08:00
hoteas a519249dfc 修正gomod 2020-06-15 14:42:53 +08:00
hoteas aa7ab49df9 修正gomod 2020-06-15 14:40:57 +08:00
hoteas 5132e67921 修正gomod 2020-06-15 14:39:28 +08:00
1265 changed files with 437548 additions and 28352 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
/.idea/*
.idea
.idea
/example/config/app.json
/example/tpt/demo/
View File
+208
View File
@@ -0,0 +1,208 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution
as defined by Sections 1 through 9 of that document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of that definition, "control" means (i) the power, direct
or indirect, to cause the direction or management of such entity, whether
by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
granted by that License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled object
code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that
is included in or attached to the work (an example is provided in the Appendix
below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of that License, Derivative
Works shall not include works that remain separable from, or merely link (or
bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative
Works thereof, that is intentionally submitted to Licensor for inclusion in
the Work by the copyright owner or by an individual or Legal Entity authorized
to submit on behalf of the copyright owner. For the purposes of that definition,
"submitted" means any form of electronic, verbal, or written communication
sent to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor
for the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently incorporated
within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of that
License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and distribute
the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of that License,
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in that section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise
transfer the Work, where such license applies only to those patent claims
licensable by such Contributor that are necessarily infringed by their Contribution(s)
alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
that the Work or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses granted to You
under that License for that Work shall terminate as of the date such litigation
is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and
in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy
of that License; and
(b) You must cause any modified files to carry prominent notices stating that
You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source
form of the Work, excluding those notices that do not pertain to any part
of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy
of the attribution notices contained within such NOTICE file, excluding those
notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part
of the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works
that You distribute, alongside or as an addendum to the NOTICE text from the
Work, provided that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction,
or distribution of Your modifications, or for any such Derivative Works as
a whole, provided Your use, reproduction, and distribution of the Work otherwise
complies with the conditions stated in that License.
5. Submission of Contributions. Unless You explicitly state otherwise, any
Contribution intentionally submitted for inclusion in the Work by You to the
Licensor shall be under the terms and conditions of that License, without
any additional terms or conditions. Notwithstanding the above, nothing herein
shall supersede or modify the terms of any separate license agreement you
may have executed with Licensor regarding such Contributions.
6. Trademarks. that License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
in writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
of using or redistributing the Work and assume any risks associated with Your
exercise of permissions under that License.
8. Limitation of Liability. In no event and under no legal theory, whether
in tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to
in writing, shall any Contributor be liable to You for damages, including
any direct, indirect, special, incidental, or consequential damages of any
character arising as a result of that License or out of the use or inability
to use the Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all other commercial
damages or losses), even if such Contributor has been advised of the possibility
of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work
or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with that License. However, in accepting such obligations,
You may act only on Your own behalf and on Your sole responsibility, not on
behalf of any other Contributor, and only if You agree to indemnify, defend,
and hold each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such warranty
or additional liability. END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own identifying
information. (Don't include the brackets!) The text should be enclosed in
the appropriate comment syntax for the file format. We also recommend that
a file or class name and description of purpose be included on the same "printed
page" as the copyright notice for easier identification within third-party
archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use that file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+6
View File
@@ -0,0 +1,6 @@
# hotime
golang web服务框架
支持数据库dbmysql、sqlite3
支持缓存cacheredismemory,数据库
自带工具类,上下文,以及session等功能
+490 -180
View File
@@ -1,9 +1,14 @@
package hotime
import (
"bytes"
. "code.hoteas.com/golang/hotime/cache"
"code.hoteas.com/golang/hotime/code"
. "code.hoteas.com/golang/hotime/common"
. "code.hoteas.com/golang/hotime/db"
. "code.hoteas.com/golang/hotime/log"
"database/sql"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"net/url"
@@ -11,61 +16,83 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
)
type Application struct {
MakeCodeRouter map[string]*code.MakeCode
MethodRouter
Router
contextBase
Error
Log *logrus.Logger
WebConnectLog *logrus.Logger
Port string //端口号
TLSPort string //ssl访问端口号
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectDbFunc func(err ...*Error) *sql.DB
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
configPath string
Config Map
Db HoTimeDB
Server *http.Server
CacheIns
sessionLong CacheIns
sessionShort CacheIns
*HoTimeCache
*http.Server
http.Handler
}
func (this *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
this.handler(w, req)
func (that *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
that.handler(w, req)
}
//启动实例
func (this *Application) Run(router Router) {
// Run 启动实例
func (that *Application) Run(router Router) {
//如果没有设置配置自动生成配置
if this.configPath == "" || len(this.Config) == 0 {
this.SetConfig()
if that.configPath == "" || len(that.Config) == 0 {
that.SetConfig()
}
//防止手动设置缓存误伤
if this.CacheIns == nil {
this.SetCache(CacheIns(&CacheMemory{}))
if that.HoTimeCache == nil {
that.SetCache()
}
//防止手动设置session误伤
if this.sessionShort == nil && this.sessionLong == nil {
if this.connectDbFunc == nil {
this.SetSession(CacheIns(&CacheMemory{}), nil)
} else {
this.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &this.Db, Time: this.Config.GetInt64("cacheLongTime")}))
//if that.sessionShort == nil && that.sessionLong == nil {
// if that.connectDbFunc == nil {
// that.SetSession(CacheIns(&CacheMemory{}), nil)
// } else {
// that.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &that.Db, Time: that.Config.GetInt64("cacheLongTime")}))
// }
//
//}
//that.Router = router
if that.Router == nil {
that.Router = Router{}
}
for k, v := range router {
if that.Router[k] == nil {
that.Router[k] = v
}
//直达接口层复用
for k1, v1 := range v {
if that.Router[k][k1] == nil {
that.Router[k][k1] = v1
}
for k2, v2 := range v1 {
that.Router[k][k1][k2] = v2
}
}
}
this.Router = router
//重新设置MethodRouter//直达路由
this.MethodRouter = MethodRouter{}
that.MethodRouter = MethodRouter{}
modeRouterStrict := true
if this.Config.Get("modeRouterStrict").(bool) == false {
if that.Config.GetBool("modeRouterStrict") == false {
modeRouterStrict = false
}
if router != nil {
for pk, pv := range router {
if that.Router != nil {
for pk, pv := range that.Router {
if !modeRouterStrict {
pk = strings.ToLower(pk)
}
@@ -79,7 +106,7 @@ func (this *Application) Run(router Router) {
if !modeRouterStrict {
mk = strings.ToLower(mk)
}
this.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
that.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
}
}
}
@@ -87,178 +114,177 @@ func (this *Application) Run(router Router) {
}
}
//this.Port = port
this.Port = this.Config.GetString("port")
this.TLSPort = this.Config.GetString("tlsPort")
//that.Port = port
that.Port = that.Config.GetString("port")
that.TLSPort = that.Config.GetString("tlsPort")
if this.connectDbFunc != nil && (this.Db.DB == nil || this.Db.DB.Ping() != nil) {
this.Db.SetConnect(this.connectDbFunc)
}
if this.CacheIns == nil {
this.CacheIns = CacheIns(&CacheMemory{Map: Map{}, Time: this.Config.GetInt64("cacheShortTime")})
if that.connectDbFunc != nil && (that.Db.DB == nil || that.Db.DB.Ping() != nil) {
that.Db.SetConnect(that.connectDbFunc)
}
//异常处理
defer func() {
if err := recover(); err != nil {
//this.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
logFmt(err, 2, LOG_ERROR)
//that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
that.Log.Warn(err)
this.Run(router)
that.Run(router)
}
}()
this.Server = &http.Server{}
that.Server = &http.Server{}
if !IsRun {
IsRun = true
}
ch := make(chan int)
if ObjToCeilInt(this.Port) != 0 {
if ObjToCeilInt(that.Port) != 0 {
go func() {
App[this.Port] = this
this.Server.Handler = this
App[that.Port] = that
that.Server.Handler = that
//启动服务
this.Server.Addr = ":" + this.Port
err := this.Server.ListenAndServe()
logFmt(err, 2)
that.Server.Addr = ":" + that.Port
err := that.Server.ListenAndServe()
that.Log.Error(err)
ch <- 1
}()
} else if ObjToCeilInt(this.TLSPort) != 0 {
}
if ObjToCeilInt(that.TLSPort) != 0 {
go func() {
App[this.TLSPort] = this
this.Server.Handler = this
App[that.TLSPort] = that
that.Server.Handler = that
//启动服务
this.Server.Addr = ":" + this.TLSPort
err := this.Server.ListenAndServeTLS(this.Config.GetString("tlsCert"), this.Config.GetString("tlsKey"))
logFmt(err, 2)
that.Server.Addr = ":" + that.TLSPort
err := that.Server.ListenAndServeTLS(that.Config.GetString("tlsCert"), that.Config.GetString("tlsKey"))
that.Log.Error(err)
ch <- 2
}()
} else {
logFmt("没有端口启用", 2, LOG_INFO)
}
if ObjToCeilInt(that.Port) == 0 && ObjToCeilInt(that.TLSPort) == 0 {
that.Log.Error("没有端口启用")
return
}
value := <-ch
logFmt("启动服务失败 : "+ObjToStr(value), 2, LOG_ERROR)
that.Log.Error("启动服务失败 : " + ObjToStr(value))
}
//启动实例
func (this *Application) SetConnectDB(connect func(err ...*Error) *sql.DB) {
// SetConnectDB 启动实例
func (that *Application) SetConnectDB(connect func(err ...*Error) (master, slave *sql.DB)) {
//this.Db.DBCached=false
//if this.Config.GetCeilInt("dbCached")!=0{
// this.Db.DBCached=true
//}
that.connectDbFunc = connect
this.connectDbFunc = connect
this.Db.SetConnect(this.connectDbFunc)
this.Db.DBCached = false
if this.Config.GetCeilInt("dbCached") != 0 {
this.Db.DBCached = true
}
this.Db.Type = this.Config.GetString("dbType")
that.Db.SetConnect(that.connectDbFunc, &that.Error)
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetSession(short CacheIns, Long CacheIns) {
this.sessionLong = Long
this.sessionShort = short
}
//默认配置缓存和session实现
func (this *Application) SetDefault(connect func(err ...*Error) *sql.DB) {
this.SetConfig()
// SetDefault 默认配置缓存和session实现
func (that *Application) SetDefault(connect func(err ...*Error) (*sql.DB, *sql.DB)) {
that.SetConfig()
if connect != nil {
this.connectDbFunc = connect
this.Db.SetConnect(this.connectDbFunc)
that.connectDbFunc = connect
that.Db.SetConnect(that.connectDbFunc)
}
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetCache(cache CacheIns) {
this.CacheIns = cache
// SetCache 设置配置文件路径全路径或者相对路径
func (that *Application) SetCache() {
cacheIns := HoTimeCache{}
cacheIns.Init(that.Config.GetMap("cache"), HoTimeDBInterface(&that.Db), &that.Error)
that.HoTimeCache = &cacheIns
//mode生产模式开启的时候才开启数据库缓存,防止调试出问题
if that.Config.GetInt("mode") == 0 {
that.Db.HoTimeCache = &cacheIns
}
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetConfig(configPath ...string) {
// SetConfig 设置配置文件路径全路径或者相对路径
func (that *Application) SetConfig(configPath ...string) {
that.Log = GetLog("", true)
that.Error = Error{Logger: that.Log}
if len(configPath) != 0 {
this.configPath = configPath[0]
that.configPath = configPath[0]
}
if this.configPath == "" {
this.configPath = "config/config.json"
if that.configPath == "" {
that.configPath = "config/config.json"
}
//加载配置文件
btes, err := ioutil.ReadFile(this.configPath)
this.Config = DeepCopyMap(Config).(Map)
btes, err := ioutil.ReadFile(that.configPath)
that.Config = DeepCopyMap(Config).(Map)
if err == nil {
cmap := Map{}
//文件是否损坏
cmap.JsonToMap(string(btes), &this.Error)
cmap.JsonToMap(string(btes), &that.Error)
for k, v := range cmap {
this.Config[k] = v //程序配置
that.Config[k] = v //程序配置
Config[k] = v //系统配置
}
} else {
logFmt("配置文件不存在,或者配置出错,使用缺省默认配置", 2)
that.Log.Error("配置文件不存在,或者配置出错,使用缺省默认配置")
}
if that.Error.GetError() != nil {
fmt.Println(that.Error.GetError().Error())
}
//文件如果损坏则不写入配置防止配置文件数据丢失
if this.Error.GetError() == nil {
var configByte bytes.Buffer
if that.Error.GetError() == nil {
//var configByte bytes.Buffer
err = json.Indent(&configByte, []byte(this.Config.ToJsonString()), "", "\t")
//判断配置文件是否序列有变化有则修改配置,五则不变
//判断配置文件是否序列有变化,有则修改配置,无则不变
//fmt.Println(len(btes))
if len(btes) != 0 && configByte.String() == string(btes) {
configStr := that.Config.ToJsonString()
if len(btes) != 0 && configStr == string(btes) {
return
}
//写入配置说明
var configNoteByte bytes.Buffer
json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
//var configNoteByte bytes.Buffer
configNoteStr := ConfigNote.ToJsonString()
//_ = json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
os.MkdirAll(filepath.Dir(this.configPath), os.ModeDir)
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
_ = os.MkdirAll(filepath.Dir(that.configPath), os.ModeDir)
err = ioutil.WriteFile(that.configPath, []byte(configStr), os.ModePerm)
if err != nil {
this.Error.SetError(err)
that.Error.SetError(err)
}
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
_ = ioutil.WriteFile(filepath.Dir(that.configPath)+"/configNote.json", []byte(configNoteStr), os.ModePerm)
}
that.Log = GetLog(that.Config.GetString("logFile"), true)
that.Error = Error{Logger: that.Log}
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
}
}
//连接判断,返回true继续传输至控制层,false则停止传输
func (this *Application) SetConnectListener(lis func(this *Context) bool) {
this.connectListener = append(this.connectListener, lis)
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) {
that.connectListener = append(that.connectListener, lis)
}
//网络错误
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
//func (that *Application) session(w http.ResponseWriter, req *http.Request) {
//
//}
//序列化链接
func (this *Application) urlSer(url string) (string, []string) {
func (that *Application) urlSer(url string) (string, []string) {
q := strings.Index(url, "?")
if q == -1 {
q = len(url)
@@ -279,9 +305,10 @@ func (this *Application) urlSer(url string) (string, []string) {
//访问
func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
nowUnixTime := time.Now()
_, s := this.urlSer(req.RequestURI)
_, s := that.urlSer(req.RequestURI)
//获取cookie
// 如果cookie存在直接将sessionId赋值为cookie.Value
// 如果cookie不存在就查找传入的参数中是否有token
@@ -289,20 +316,24 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
// 如果token存在就判断token是否在Session中有保存
// 如果有取出token并复制给cookie
// 没有保存就生成随机的session
cookie, err := req.Cookie(this.Config.GetString("sessionName"))
cookie, err := req.Cookie(that.Config.GetString("sessionName"))
sessionId := Md5(strconv.Itoa(Rand(10)))
token := req.FormValue("token")
//isFirst:=false
if err != nil || (len(token) == 32 && cookie.Value != token) {
if len(token) == 32 {
sessionId = token
}
//else{
// isFirst=true;
//}
http.SetCookie(w, &http.Cookie{Name: this.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
} else {
needSetCookie := ""
token := req.Header.Get("Authorization")
if len(token) != 32 {
token = req.FormValue("token")
}
//没有cookie或者cookie不等于token
//有token优先token
if len(token) == 32 {
sessionId = token
//没有token,则查阅session
} else if err == nil && cookie.Value != "" {
sessionId = cookie.Value
//session也没有则判断是否创建cookie
} else {
needSetCookie = sessionId
}
unescapeUrl, err := url.QueryUnescape(req.RequestURI)
@@ -310,71 +341,77 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
unescapeUrl = req.RequestURI
}
//访问实例
context := Context{SessionIns: SessionIns{SessionId: sessionId,
LongCache: this.sessionLong,
ShortCache: this.sessionShort,
},
CacheIns: this.CacheIns,
Resp: w, Req: req, Application: this, RouterString: s, Config: this.Config, Db: &this.Db, HandlerStr: unescapeUrl}
context := Context{SessionIns: SessionIns{SessionId: sessionId, HoTimeCache: that.HoTimeCache},
Resp: w, Req: req, Application: that, RouterString: s, Config: that.Config, Db: &that.Db,
HandlerStr: unescapeUrl}
//header默认设置
header := w.Header()
header.Set("Content-Type", "text/html; charset=utf-8")
//url去掉参数并序列化
context.HandlerStr, context.RouterString = this.urlSer(context.HandlerStr)
context.HandlerStr, context.RouterString = that.urlSer(context.HandlerStr)
//跨域设置
this.crossDomain(&context)
//是否展示日志
if this.Config.GetInt("connectLogShow") != 0 {
logFmt(Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))+" "+context.HandlerStr, 0, LOG_INFO)
}
that.crossDomain(&context, needSetCookie)
defer func() {
//是否展示日志
if that.WebConnectLog != nil {
//负载均衡优化
ipStr := ""
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")
}
//负载均衡优化
if ipStr == "" {
//RemoteAddr := that.Req.RemoteAddr
ipStr = Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))
}
that.WebConnectLog.Infoln(ipStr, context.Req.Method,
"time cost:", ObjToFloat64(time.Now().UnixNano()-nowUnixTime.UnixNano())/1000000.00, "ms",
"data length:", ObjToFloat64(context.DataSize)/1000.00, "KB", context.HandlerStr)
}
}()
//访问拦截true继续false暂停
connectListenerLen := len(this.connectListener)
if connectListenerLen != 0 {
for i := 0; i < connectListenerLen; i++ {
connectListenerLen := len(that.connectListener) - 1
if !this.connectListener[i](&context) {
context.View()
return
}
for true {
if connectListenerLen < 0 {
break
}
if that.connectListener[connectListenerLen](&context) {
context.View()
return
}
connectListenerLen--
}
//接口服务
//if len(s) == 3 {
// //如果满足规则则路由到对应控制器去
// if this.Router[s[0]] != nil && this.Router[s[0]][s[1]] != nil && this.Router[s[0]][s[1]][s[2]] != nil {
// //控制层
// this.Router[s[0]][s[1]][s[2]](&context)
// //header.Set("Content-Type", "text/html; charset=utf-8")
// context.View()
// return
// }
//
//}
//验证接口严格模式
modeRouterStrict := this.Config.Get("modeRouterStrict").(bool)
modeRouterStrict := that.Config.GetBool("modeRouterStrict")
tempHandlerStr := context.HandlerStr
if !modeRouterStrict {
tempHandlerStr = strings.ToLower(tempHandlerStr)
}
//执行接口
if this.MethodRouter[tempHandlerStr] != nil {
this.MethodRouter[tempHandlerStr](&context)
if that.MethodRouter[tempHandlerStr] != nil {
that.MethodRouter[tempHandlerStr](&context)
context.View()
return
}
//url赋值
path := this.Config.GetString("tpt") + tempHandlerStr
path := that.Config.GetString("tpt") + tempHandlerStr
//判断是否为默认
if path[len(path)-1] == '/' {
defFile := this.Config.GetSlice("defFile")
defFile := that.Config.GetSlice("defFile")
for i := 0; i < len(defFile); i++ {
temp := path + defFile.GetString(i)
@@ -398,12 +435,20 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
//设置header
delete(header, "Content-Type")
if this.Config.GetInt("debug") != 1 {
if that.Config.GetInt("mode") == 0 {
header.Set("Cache-Control", "public")
} else {
header.Set("Cache-Control", "no-cache")
}
if strings.Index(path, ".m3u8") != -1 {
header.Add("Content-Type", "audio/mpegurl")
t := strings.LastIndex(path, ".")
if t != -1 {
tt := path[t:]
if MimeMaps[tt] != "" {
header.Add("Content-Type", MimeMaps[tt])
}
}
//w.Write(data)
@@ -411,32 +456,66 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
}
func (this *Application) crossDomain(context *Context) {
func (that *Application) crossDomain(context *Context, sessionId string) {
//没有跨域设置
if context.Config.GetString("crossDomain") == "" {
if sessionId != "" {
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
}
return
}
header := context.Resp.Header()
header.Set("Access-Control-Allow-Origin", "*")
header.Set("Access-Control-Allow-Methods", "*")
header.Set("Access-Control-Allow-Credentials", "true")
header.Set("Access-Control-Expose-Headers", "*")
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
if context.Config.GetString("crossDomain") != "*" {
header.Set("Access-Control-Allow-Origin", this.Config.GetString("crossDomain"))
//不跨域,则不设置
remoteHost := context.Req.Host
if context.Config.GetString("port") == "80" || context.Config.GetString("port") == "443" {
remoteHost = remoteHost + ":" + context.Config.GetString("port")
}
if context.Config.GetString("crossDomain") != "auto" {
//不跨域,则不设置
if strings.Contains(context.Config.GetString("crossDomain"), remoteHost) {
if sessionId != "" {
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
}
return
}
header.Set("Access-Control-Allow-Origin", that.Config.GetString("crossDomain"))
// 后端设置,2592000单位秒,这里是30天
header.Set("Access-Control-Max-Age", "2592000")
//header.Set("Access-Control-Allow-Origin", "*")
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
header.Set("Access-Control-Allow-Credentials", "true")
header.Set("Access-Control-Expose-Headers", "*")
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
if sessionId != "" {
//跨域允许需要设置cookie的允许跨域https才有效果
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
}
return
}
origin := context.Req.Header.Get("Origin")
if origin != "" {
header.Set("Access-Control-Allow-Origin", origin)
refer := context.Req.Header.Get("Referer")
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
if sessionId != "" {
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
}
return
}
refer := context.Req.Header.Get("Referer")
if refer != "" {
if origin != "" {
header.Set("Access-Control-Allow-Origin", origin)
//return
} else if refer != "" {
tempInt := 0
lastInt := strings.IndexFunc(refer, func(r rune) bool {
if r == '/' && tempInt > 8 {
@@ -451,5 +530,236 @@ func (this *Application) crossDomain(context *Context) {
}
refer = Substr(refer, 0, lastInt)
header.Set("Access-Control-Allow-Origin", refer)
//header.Set("Access-Control-Allow-Origin", "*")
}
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
header.Set("Access-Control-Allow-Credentials", "true")
header.Set("Access-Control-Expose-Headers", "*")
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
if sessionId != "" {
//跨域允许需要设置cookie的允许跨域https才有效果
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
}
}
//Init 初始化application
func Init(config string) *Application {
appIns := Application{}
//手动模式,
appIns.SetConfig(config)
SetDB(&appIns)
appIns.SetCache()
codeConfig := appIns.Config.GetSlice("codeConfig")
if codeConfig != nil {
for k, _ := range codeConfig {
codeMake := codeConfig.GetMap(k)
if codeMake == nil {
continue
}
//codeMake["table"] = k
if appIns.MakeCodeRouter == nil {
appIns.MakeCodeRouter = map[string]*code.MakeCode{}
}
if codeMake.GetString("name") == "" {
codeMake["name"] = codeMake.GetString("table")
}
appIns.MakeCodeRouter[codeMake.GetString("name")] = &code.MakeCode{Error: appIns.Error}
appIns.MakeCodeRouter[codeMake.GetString("name")].Db2JSON(&appIns.Db, codeMake)
//接入动态代码层
if appIns.Router == nil {
appIns.Router = Router{}
}
//appIns.Router[codeMake.GetString("name")] = TptProject
appIns.Router[codeMake.GetString("name")] = Proj{}
for k2, _ := range TptProject {
appIns.Router[codeMake.GetString("name")][k2] = Ctr{}
for k3, v3 := range TptProject[k2] {
appIns.Router[codeMake.GetString("name")][k2][k3] = v3
}
}
for k1, _ := range appIns.MakeCodeRouter[codeMake.GetString("name")].TableColumns {
appIns.Router[codeMake.GetString("name")][k1] = appIns.Router[codeMake.GetString("name")]["hotimeCommon"]
}
setMakeCodeListener(codeMake.GetString("name"), &appIns)
}
}
return &appIns
}
// SetDB 智能数据库设置
func SetDB(appIns *Application) {
db := appIns.Config.GetMap("db")
dbSqlite := db.GetMap("sqlite")
dbMysql := db.GetMap("mysql")
if db != nil && dbSqlite != nil {
SetSqliteDB(appIns, dbSqlite)
}
if db != nil && dbMysql != nil {
SetMysqlDB(appIns, dbMysql)
}
}
func SetMysqlDB(appIns *Application, config Map) {
appIns.Db.Type = "mysql"
appIns.Db.DBName = config.GetString("name")
appIns.Db.Prefix = config.GetString("prefix")
appIns.Db.Log = appIns.Log
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
//master数据库配置
query := config.GetString("user") + ":" + config.GetString("password") +
"@tcp(" + config.GetString("host") + ":" + config.GetString("port") + ")/" + config.GetString("name") + "?charset=utf8"
DB, e := sql.Open("mysql", query)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
master = DB
//slave数据库配置
configSlave := config.GetMap("slave")
if configSlave != nil {
query := configSlave.GetString("user") + ":" + configSlave.GetString("password") +
"@tcp(" + config.GetString("host") + ":" + configSlave.GetString("port") + ")/" + configSlave.GetString("name") + "?charset=utf8"
DB1, e := sql.Open("mysql", query)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
slave = DB1
}
return master, slave
//return DB
})
}
func SetSqliteDB(appIns *Application, config Map) {
appIns.Db.Type = "sqlite"
appIns.Db.Prefix = config.GetString("prefix")
appIns.Db.Mode = appIns.Config.GetCeilInt("mode")
appIns.Db.Log = appIns.Log
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
db, e := sql.Open("sqlite3", config.GetString("path"))
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
master = db
return master, slave
})
}
func setMakeCodeListener(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.RouterString[1] == "hotime" && context.RouterString[2] == "wallpaper" {
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
})
}
+277
View File
@@ -0,0 +1,277 @@
package cache
import (
. "code.hoteas.com/golang/hotime/common"
"errors"
)
// HoTimeCache 可配置memorydbredis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
//缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
type HoTimeCache struct {
*Error
dbCache *CacheDb
redisCache *CacheRedis
memoryCache *CacheMemory
Config Map
}
func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
var reData *Obj
if len(data) == 0 {
//内存缓存有
if that.memoryCache != nil && that.memoryCache.SessionSet {
reData = that.memoryCache.Cache(key, data...)
if reData.Data != nil {
return reData
}
}
//redis缓存有
if that.redisCache != nil && that.redisCache.SessionSet {
reData = that.redisCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.SessionSet {
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
}
//db缓存有
if that.dbCache != nil && that.dbCache.SessionSet {
reData = that.dbCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.SessionSet {
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil && that.redisCache.SessionSet {
that.redisCache.Cache(key, reData.Data)
}
return reData
}
}
return reData
}
//设置缓存
//设置内存缓存
if that.memoryCache != nil && that.memoryCache.SessionSet {
reData = that.memoryCache.Cache(key, data...)
}
//redis缓存有
if that.redisCache != nil && that.redisCache.SessionSet {
reData = that.redisCache.Cache(key, data...)
}
//redis缓存有
if that.dbCache != nil && that.dbCache.SessionSet {
reData = that.dbCache.Cache(key, data...)
}
return reData
}
func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
var reData *Obj
if len(data) == 0 {
//内存缓存有
if that.memoryCache != nil && that.memoryCache.DbSet {
reData = that.memoryCache.Cache(key, data...)
if reData.Data != nil {
return reData
}
}
//redis缓存有
if that.redisCache != nil && that.redisCache.DbSet {
reData = that.redisCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.DbSet {
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
}
//redis缓存有
if that.dbCache != nil && that.dbCache.DbSet {
reData = that.dbCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil && that.memoryCache.DbSet {
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil && that.redisCache.DbSet {
that.redisCache.Cache(key, reData.Data)
}
return reData
}
}
return reData
}
//设置缓存
//设置内存缓存
if that.memoryCache != nil && that.memoryCache.DbSet {
reData = that.memoryCache.Cache(key, data...)
}
//redis缓存有
if that.redisCache != nil && that.redisCache.DbSet {
reData = that.redisCache.Cache(key, data...)
}
//redis缓存有
if that.dbCache != nil && that.dbCache.DbSet {
reData = that.dbCache.Cache(key, data...)
}
return reData
}
func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
var reData *Obj
if len(data) == 0 {
//内存缓存有
if that.memoryCache != nil {
reData = that.memoryCache.Cache(key, data...)
if reData != nil {
return reData
}
}
//redis缓存有
if that.redisCache != nil {
reData = that.redisCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil {
that.memoryCache.Cache(key, reData.Data)
}
return reData
}
}
//redis缓存有
if that.dbCache != nil {
reData = that.dbCache.Cache(key, data...)
if reData.Data != nil {
if that.memoryCache != nil {
that.memoryCache.Cache(key, reData.Data)
}
if that.redisCache != nil {
that.redisCache.Cache(key, reData.Data)
}
return reData
}
}
return reData
}
//设置缓存
//设置内存缓存
if that.memoryCache != nil {
reData = that.memoryCache.Cache(key, data...)
}
//redis缓存有
if that.redisCache != nil {
reData = that.redisCache.Cache(key, data...)
}
//redis缓存有
if that.dbCache != nil {
reData = that.dbCache.Cache(key, data...)
}
return reData
}
func (that *HoTimeCache) Init(config Map, hotimeDb HoTimeDBInterface, err ...*Error) {
//防止空数据问题
if config == nil {
config = Map{}
}
if err[0] != nil {
that.Error = err[0]
}
that.Config = config
//memory配置初始化
memory := that.Config.GetMap("memory")
if memory == nil {
memory = Map{
"db": true,
"session": true,
"sort": 0,
"timeout": 60 * 60 * 2,
}
}
if memory.Get("db") == nil {
memory["db"] = true
}
if memory.Get("session") == nil {
memory["session"] = true
}
if memory.Get("timeout") == nil {
memory["timeout"] = 60 * 60 * 2
}
that.Config["memory"] = memory
that.memoryCache = &CacheMemory{TimeOut: memory.GetCeilInt64("timeout"),
DbSet: memory.GetBool("db"), SessionSet: memory.GetBool("session")}
if err[0] != nil {
that.memoryCache.SetError(err[0])
}
//db配置初始化
redis := that.Config.GetMap("redis")
if redis != nil {
if redis.GetString("host") == "" || redis.GetString("port") == "" {
if err[0] != nil {
err[0].SetError(errors.New("请检查redis配置host和port配置"))
}
return
}
if redis.Get("db") == nil {
redis["db"] = true
}
if redis.Get("session") == nil {
redis["session"] = true
}
if redis.Get("timeout") == nil {
redis["timeout"] = 60 * 60 * 24 * 15
}
that.Config["redis"] = redis
that.redisCache = &CacheRedis{TimeOut: redis.GetCeilInt64("timeout"),
DbSet: redis.GetBool("db"), SessionSet: redis.GetBool("session"), Host: redis.GetString("host"),
Pwd: redis.GetString("password"), Port: redis.GetCeilInt64("port")}
if err[0] != nil {
that.redisCache.SetError(err[0])
}
}
//db配置初始化
db := that.Config.GetMap("db")
if db != nil {
if db.Get("db") == nil {
db["db"] = false
}
if db.Get("session") == nil {
db["session"] = true
}
if db.Get("timeout") == nil {
db["timeout"] = 60 * 60 * 24 * 30
}
that.Config["db"] = db
that.dbCache = &CacheDb{TimeOut: db.GetCeilInt64("timeout"),
DbSet: db.GetBool("db"), SessionSet: db.GetBool("session"),
Db: hotimeDb}
if err[0] != nil {
that.dbCache.SetError(err[0])
}
}
}
+173
View File
@@ -0,0 +1,173 @@
package cache
import (
. "code.hoteas.com/golang/hotime/common"
"database/sql"
"encoding/json"
"strings"
"time"
)
type HoTimeDBInterface interface {
GetPrefix() string
Query(query string, args ...interface{}) []Map
Exec(query string, args ...interface{}) (sql.Result, *Error)
Get(table string, qu ...interface{}) Map
Select(table string, qu ...interface{}) []Map
Delete(table string, data map[string]interface{}) int64
Update(table string, data Map, where Map) int64
Insert(table string, data map[string]interface{}) int64
GetType() string
}
type CacheDb struct {
TimeOut int64
DbSet bool
SessionSet bool
Db HoTimeDBInterface
*Error
ContextBase
isInit bool
}
func (that *CacheDb) GetError() *Error {
return that.Error
}
func (that *CacheDb) SetError(err *Error) {
that.Error = err
}
func (that *CacheDb) initDbTable() {
if that.isInit {
return
}
if that.Db.GetType() == "mysql" {
dbNames := that.Db.Query("SELECT DATABASE()")
if len(dbNames) == 0 {
return
}
dbName := dbNames[0].GetString("DATABASE()")
res := that.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='" + that.Db.GetPrefix() + "cached'")
if len(res) != 0 {
that.isInit = true
return
}
_, e := that.Db.Exec("CREATE TABLE `" + that.Db.GetPrefix() + "cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `key` varchar(60) DEFAULT NULL, `value` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
if e.GetError() == nil {
that.isInit = true
}
}
if that.Db.GetType() == "sqlite" {
res := that.Db.Query(`select * from sqlite_master where type = 'table' and name = '` + that.Db.GetPrefix() + `cached'`)
if len(res) != 0 {
that.isInit = true
return
}
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"key" TEXT(60),
"value" TEXT(2000),
"time" integer,
"endtime" integer
);`)
if e.GetError() == nil {
that.isInit = true
}
}
}
//获取Cache键只能为string类型
func (that *CacheDb) get(key string) interface{} {
cached := that.Db.Get("cached", "*", Map{"key": key})
if cached == nil {
return nil
}
//data:=cacheMap[key];
if cached.GetInt64("endtime") <= time.Now().Unix() {
that.Db.Delete("cached", Map{"id": cached.GetString("id")})
return nil
}
data := Map{}
data.JsonToMap(cached.GetString("value"))
return data.Get("data")
}
//key value ,时间为时间戳
func (that *CacheDb) set(key string, value interface{}, tim int64) {
bte, _ := json.Marshal(Map{"data": value})
num := that.Db.Update("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"key": key})
if num == int64(0) {
that.Db.Insert("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "key": key})
}
//随机执行删除命令
if Rand(1000) > 950 {
that.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
}
}
func (that *CacheDb) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
that.Db.Delete("cached", Map{"key": key + "%"})
} else {
that.Db.Delete("cached", Map{"key": key})
}
}
func (that *CacheDb) Cache(key string, data ...interface{}) *Obj {
that.initDbTable()
if len(data) == 0 {
return &Obj{Data: that.get(key)}
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
that.delete(key)
return &Obj{Data: nil}
}
if len(data) == 1 {
if that.TimeOut == 0 {
//that.Time = Config.GetInt64("cacheLongTime")
}
tim += that.TimeOut
}
if len(data) == 2 {
that.SetError(nil)
tempt := ObjToInt64(data[1], that.Error)
if tempt > tim {
tim = tempt
} else if that.GetError() == nil {
tim = tim + tempt
}
}
that.set(key, data[0], tim)
return &Obj{Data: nil}
}
+158
View File
@@ -0,0 +1,158 @@
package cache
import (
. "code.hoteas.com/golang/hotime/common"
"strings"
"sync"
"time"
)
type CacheMemory struct {
TimeOut int64
DbSet bool
SessionSet bool
Map
*Error
ContextBase
mutex *sync.RWMutex
}
func (that *CacheMemory) GetError() *Error {
return that.Error
}
func (that *CacheMemory) SetError(err *Error) {
that.Error = err
}
//获取Cache键只能为string类型
func (that *CacheMemory) get(key string) interface{} {
that.Error.SetError(nil)
if that.Map == nil {
that.Map = Map{}
}
if that.Map[key] == nil {
return nil
}
data := that.Map.Get(key, that.Error).(cacheData)
if that.Error.GetError() != nil {
return nil
}
if data.time < time.Now().Unix() {
delete(that.Map, key)
return nil
}
return data.data
}
func (that *CacheMemory) refreshMap() {
go func() {
that.mutex.Lock()
defer that.mutex.Unlock()
for key, v := range that.Map {
data := v.(cacheData)
if data.time <= time.Now().Unix() {
delete(that.Map, key)
}
}
}()
}
//key value ,时间为时间戳
func (that *CacheMemory) set(key string, value interface{}, time int64) {
that.Error.SetError(nil)
var data cacheData
if that.Map == nil {
that.Map = Map{}
}
dd := that.Map[key]
if dd == nil {
data = cacheData{}
} else {
data = dd.(cacheData)
}
data.time = time
data.data = value
that.Map.Put(key, data)
}
func (that *CacheMemory) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
for k, _ := range that.Map {
if strings.Index(k, key) != -1 {
delete(that.Map, k)
}
}
} else {
delete(that.Map, key)
}
}
func (that *CacheMemory) Cache(key string, data ...interface{}) *Obj {
x := RandX(1, 100000)
if x > 99950 {
that.refreshMap()
}
if that.mutex == nil {
that.mutex = &sync.RWMutex{}
}
reData := &Obj{Data: nil}
if len(data) == 0 {
that.mutex.RLock()
reData.Data = that.get(key)
that.mutex.RUnlock()
return reData
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
that.mutex.Lock()
that.delete(key)
that.mutex.Unlock()
return reData
}
if len(data) == 1 {
tim = tim + that.TimeOut
}
if len(data) == 2 {
that.Error.SetError(nil)
tempt := ObjToInt64(data[1], that.Error)
if tempt > tim {
tim = tempt
} else if that.Error.GetError() == nil {
tim = tim + tempt
}
}
that.mutex.Lock()
that.set(key, data[0], tim)
that.mutex.Unlock()
return reData
}
+71 -54
View File
@@ -1,67 +1,84 @@
package cache
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
"github.com/garyburd/redigo/redis"
"strings"
"time"
)
type CacheRedis struct {
Host string
Pwd string
Time int64
conn redis.Conn
tag int64
Error
TimeOut int64
DbSet bool
SessionSet bool
Host string
Pwd string
Port int64
conn redis.Conn
tag int64
ContextBase
*Error
}
func (that *CacheRedis) GetError() *Error {
return that.Error
}
func (that *CacheRedis) SetError(err *Error) {
that.Error = err
}
//唯一标志
func (this *CacheRedis) GetTag() int64 {
func (that *CacheRedis) GetTag() int64 {
if this.tag == int64(0) {
this.tag = time.Now().UnixNano()
if that.tag == int64(0) {
that.tag = time.Now().UnixNano()
}
return this.tag
return that.tag
}
func (this *CacheRedis) reCon() bool {
func (that *CacheRedis) reCon() bool {
var err error
this.conn, err = redis.Dial("tcp", this.Host)
that.conn, err = redis.Dial("tcp", that.Host+":"+ObjToStr(that.Port))
if err != nil {
this.conn = nil
this.Error.SetError(err)
that.conn = nil
that.Error.SetError(err)
return false
}
_, err = this.conn.Do("AUTH", this.Pwd)
if err != nil {
this.conn = nil
this.Error.SetError(err)
return false
if that.Pwd != "" {
_, err = that.conn.Do("AUTH", that.Pwd)
if err != nil {
that.conn = nil
that.Error.SetError(err)
return false
}
}
return true
}
func (this *CacheRedis) del(key string) {
func (that *CacheRedis) del(key string) {
del := strings.Index(key, "*")
if del != -1 {
val, err := redis.Strings(this.conn.Do("KEYS", key))
val, err := redis.Strings(that.conn.Do("KEYS", key))
if err != nil {
return
}
this.conn.Send("MULTI")
that.conn.Send("MULTI")
for i, _ := range val {
this.conn.Send("DEL", val[i])
that.conn.Send("DEL", val[i])
}
this.conn.Do("EXEC")
that.conn.Do("EXEC")
} else {
_, err := this.conn.Do("DEL", key)
_, err := that.conn.Do("DEL", key)
if err != nil {
this.Error.SetError(err)
_, err = this.conn.Do("PING")
that.Error.SetError(err)
_, err = that.conn.Do("PING")
if err != nil {
if this.reCon() {
_, err = this.conn.Do("DEL", key)
if that.reCon() {
_, err = that.conn.Do("DEL", key)
}
}
}
@@ -69,32 +86,32 @@ func (this *CacheRedis) del(key string) {
}
//key value ,时间为时间戳
func (this *CacheRedis) set(key string, value string, time int64) {
_, err := this.conn.Do("SET", key, value, "EX", ObjToStr(time))
func (that *CacheRedis) set(key string, value string, time int64) {
_, err := that.conn.Do("SET", key, value, "EX", ObjToStr(time))
if err != nil {
this.Error.SetError(err)
_, err = this.conn.Do("PING")
that.Error.SetError(err)
_, err = that.conn.Do("PING")
if err != nil {
if this.reCon() {
_, err = this.conn.Do("SET", key, value, "EX", ObjToStr(time))
if that.reCon() {
_, err = that.conn.Do("SET", key, value, "EX", ObjToStr(time))
}
}
}
}
func (this *CacheRedis) get(key string) *Obj {
func (that *CacheRedis) get(key string) *Obj {
reData := &Obj{}
var err error
reData.Data, err = redis.String(this.conn.Do("GET", key))
reData.Data, err = redis.String(that.conn.Do("GET", key))
if err != nil {
reData.Data = nil
if !strings.Contains(err.Error(), "nil returned") {
this.Error.SetError(err)
_, err = this.conn.Do("PING")
that.Error.SetError(err)
_, err = that.conn.Do("PING")
if err != nil {
if this.reCon() {
reData.Data, err = redis.String(this.conn.Do("GET", key))
if that.reCon() {
reData.Data, err = redis.String(that.conn.Do("GET", key))
}
}
@@ -104,10 +121,10 @@ func (this *CacheRedis) get(key string) *Obj {
return reData
}
func (this *CacheRedis) Cache(key string, data ...interface{}) *Obj {
func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
reData := &Obj{}
if this.conn == nil {
re := this.reCon()
if that.conn == nil {
re := that.reCon()
if !re {
return reData
}
@@ -115,38 +132,38 @@ func (this *CacheRedis) Cache(key string, data ...interface{}) *Obj {
//查询缓存
if len(data) == 0 {
reData = this.get(key)
reData = that.get(key)
return reData
}
tim := int64(0)
//删除缓存
if len(data) == 1 && data[0] == nil {
this.del(key)
that.del(key)
return reData
}
//添加缓存
if len(data) == 1 {
if this.Time == 0 {
this.Time = Config.GetInt64("cacheShortTime")
if that.TimeOut == 0 {
//that.Time = Config.GetInt64("cacheShortTime")
}
tim += this.Time
tim += that.TimeOut
}
if len(data) == 2 {
this.Error.SetError(nil)
tempt := ObjToInt64(data[1], &this.Error)
that.Error.SetError(nil)
tempt := ObjToInt64(data[1], that.Error)
if tempt > tim {
tim = tempt
} else if this.GetError() == nil {
} else if that.GetError() == nil {
tim = tim + tempt
}
}
this.set(key, ObjToStr(data[0]), tim)
that.set(key, ObjToStr(data[0]), tim)
return reData
@@ -1,16 +1,15 @@
package hotime
package cache
//控制器
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)
import (
. "code.hoteas.com/golang/hotime/common"
)
type CacheIns interface {
//set(key string, value interface{}, time int64)
//get(key string) interface{}
//delete(key string)
GetError() *Error
SetError(err *Error)
Cache(key string, data ...interface{}) *Obj
}
-146
View File
@@ -1,146 +0,0 @@
package hotime
import (
"encoding/json"
"strings"
"time"
)
type CacheDb struct {
Time int64
Db *HoTimeDB
contextBase
isInit bool
}
func (this *CacheDb) initDbTable() {
if this.isInit {
return
}
if this.Db.Type == "mysql" {
dbNames := this.Db.Query("SELECT DATABASE()")
if len(dbNames) == 0 {
return
}
dbName := dbNames[0].GetString("DATABASE()")
res := this.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='cached'")
if len(res) != 0 {
this.isInit = true
return
}
_, e := this.Db.Exec("CREATE TABLE `cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
if e.GetError() == nil {
this.isInit = true
}
}
if this.Db.Type == "sqlite" {
res := this.Db.Query(`select * from sqlite_master where type = 'table' and name = 'cached'`)
if len(res) != 0 {
this.isInit = true
return
}
_, e := this.Db.Exec(`CREATE TABLE "cached" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"ckey" TEXT(60),
"cvalue" TEXT(2000),
"time" integer,
"endtime" integer
);`)
if e.GetError() == nil {
this.isInit = true
}
}
}
//获取Cache键只能为string类型
func (this *CacheDb) get(key string) interface{} {
cached := this.Db.Get("cached", "*", Map{"ckey": key})
if cached == nil {
return nil
}
//data:=cacheMap[key];
if cached.GetInt64("endtime") <= time.Now().Unix() {
this.Db.Delete("cached", Map{"id": cached.GetString("id")})
return nil
}
data := Map{}
data.JsonToMap(cached.GetString("cvalue"))
return data.Get("data")
}
//key value ,时间为时间戳
func (this *CacheDb) set(key string, value interface{}, tim int64) {
bte, _ := json.Marshal(Map{"data": value})
num := this.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
if num == int64(0) {
this.Db.Insert("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
}
//随机执行删除命令
if Rand(1000) > 950 {
this.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
}
}
func (this *CacheDb) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
this.Db.Delete("cached", Map{"ckey": key + "%"})
} else {
this.Db.Delete("cached", Map{"ckey": key})
}
}
func (this *CacheDb) Cache(key string, data ...interface{}) *Obj {
this.initDbTable()
if len(data) == 0 {
return &Obj{Data: this.get(key)}
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
this.delete(key)
return &Obj{Data: nil}
}
if len(data) == 1 {
if this.Time == 0 {
this.Time = Config.GetInt64("cacheLongTime")
}
tim += this.Time
}
if len(data) == 2 {
this.SetError(nil)
tempt := ObjToInt64(data[1], &this.Error)
if tempt > tim {
tim = tempt
} else if this.GetError() == nil {
tim = tim + tempt
}
}
this.set(key, data[0], tim)
return &Obj{Data: nil}
}
-146
View File
@@ -1,146 +0,0 @@
package hotime
import (
"strings"
"sync"
"time"
)
type CacheMemory struct {
Time int64
Map
contextBase
mutex *sync.RWMutex
}
//获取Cache键只能为string类型
func (this *CacheMemory) get(key string) interface{} {
this.Error.SetError(nil)
if this.Map == nil {
this.Map = Map{}
}
if this.Map[key] == nil {
return nil
}
data := this.Map.Get(key, &this.Error).(cacheData)
if this.GetError() != nil {
return nil
}
if data.time < time.Now().Unix() {
delete(this.Map, key)
return nil
}
return data.data
}
func (this *CacheMemory) refreshMap() {
go func() {
this.mutex.Lock()
defer this.mutex.Unlock()
for key, v := range this.Map {
data := v.(cacheData)
if data.time <= time.Now().Unix() {
delete(this.Map, key)
}
}
}()
}
//key value ,时间为时间戳
func (this *CacheMemory) set(key string, value interface{}, time int64) {
this.Error.SetError(nil)
var data cacheData
if this.Map == nil {
this.Map = Map{}
}
dd := this.Map[key]
if dd == nil {
data = cacheData{}
} else {
data = dd.(cacheData)
}
data.time = time
data.data = value
this.Map.Put(key, data)
}
func (this *CacheMemory) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
for k, _ := range this.Map {
if strings.Index(k, key) != -1 {
delete(this.Map, key)
}
}
} else {
delete(this.Map, key)
}
}
func (this *CacheMemory) Cache(key string, data ...interface{}) *Obj {
x := RandX(1, 100000)
if x > 99950 {
this.refreshMap()
}
if this.mutex == nil {
this.mutex = &sync.RWMutex{}
}
reData := &Obj{Data: nil}
if len(data) == 0 {
this.mutex.RLock()
reData.Data = this.get(key)
this.mutex.RUnlock()
return reData
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
this.mutex.Lock()
this.delete(key)
this.mutex.Unlock()
return reData
}
if len(data) == 1 {
if this.Time == 0 {
this.Time = Config.GetInt64("cacheShortTime")
}
tim = tim + this.Time
}
if len(data) == 2 {
this.Error.SetError(nil)
tempt := ObjToInt64(data[1], &this.Error)
if tempt > tim {
tim = tempt
} else if this.GetError() == nil {
tim = tim + tempt
}
}
this.mutex.Lock()
this.set(key, data[0], tim)
this.mutex.Unlock()
return reData
}
+1053
View File
File diff suppressed because it is too large Load Diff
+188
View File
@@ -0,0 +1,188 @@
package code
import (
. "code.hoteas.com/golang/hotime/common"
)
var Config = Map{
"name": "HoTimeDashBoard",
//"id": "2f92h3herh23rh2y8",
"label": "HoTime管理平台",
"stop": Slice{"role", "org"}, //不更新的,同时不允许修改用户自身对应的表数据
"labelConfig": Map{
"show": "开启",
"add": "添加",
"delete": "删除",
"edit": "编辑",
"info": "查看详情",
"download": "下载清单",
},
"menus": []Map{
//{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
//{"label": "测试表格", "table": "table", "icon": "el-icon-suitcase"},
//{"label": "系统管理", "name": "setting", "icon": "el-icon-setting",
// "menus": []Map{
// {"label": "用户管理", "table": "user",
// "default": {
// "path": "info",
// "id": "1"
// },
// "auth": ["show","edit","info","add","delete"],
// },
// {"label": "组织管理", "table": "organization"},
// {"label": "地区管理", "table": "area"},
// {"label": "角色管理", "table": "role"},
// {"label": "日志管理", "table": "log"},
// {"label": "系统设置", "table": "system", "default": "edit"},
// },
//},
},
}
var DefaultMenuParentName = "sys"
var ColumnDataType = map[string]string{
//sqlite专有类型
"real": "number",
//mysql数据类型宽泛类型
"int": "number",
"float": "number",
"double": "number",
"decimal": "number",
"integer": "number", //sqlite3
"char": "text",
"text": "text",
"blob": "text",
"date": "time",
"time": "time",
"year": "time",
"geometry": "gis", //不建议使用gis类型,建议使用其他代替
}
type ColumnShow struct {
Name string //名称
List bool //列表权限
Edit bool //新增和编辑权限
Info bool //详情权限
Must bool //字段全匹配
Type string //空字符串表示
Strict bool //name严格匹配必须是这个词才行
}
var RuleConfig = []Map{
{"name": "idcard", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "id", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": true, "type": ""},
{"name": "sn", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": ""},
{"name": "parent_ids", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
{"name": "index", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
{"name": "parent_id", "add": true, "list": true, "edit": true, "info": true, "must": false, "true": false, "type": ""},
{"name": "amount", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": true, "type": "money"},
{"name": "info", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "textArea"},
{"name": "status", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
{"name": "state", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
{"name": "sex", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
{"name": "delete", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
{"name": "lat", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "lng", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "latitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "longitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "password", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
{"name": "pwd", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
{"name": "version", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
{"name": "seq", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "sort", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "note", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "description", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "abstract", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "content", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "textArea"},
{"name": "address", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "full_name", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "create_time", "add": false, "list": false, "edit": false, "info": true, "must": false, "strict": true, "type": "time"},
{"name": "modify_time", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": true, "type": "time"},
{"name": "image", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
{"name": "img", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
{"name": "avatar", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
{"name": "icon", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "image"},
{"name": "file", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "file"},
{"name": "age", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "email", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
{"name": "time", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "time"},
{"name": "level", "add": false, "list": false, "edit": false, "info": true, "must": false, "strict": false, "type": ""},
{"name": "rule", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "form"},
{"name": "auth", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "auth"},
{"name": "table", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": "table"},
{"name": "table_id", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": "table_id"},
}
//var ColumnNameType = []ColumnShow{
// //通用
// {"idcard", false, true, true, false, "", false},
// {"id", true, false, true, false, "", true},
// {"sn", true, false, true, false, "", false},
// {"parent_ids", false, false, false, false, "index", true},
// {"parent_id", true, true, true, false, "", true},
// {"amount", true, true, true, false, "money", true},
// {"info", false, true, true, false, "textArea", false},
// //"sn"{true,true,true,""},
// {"status", true, true, true, false, "select", false},
// {"state", true, true, true, false, "select", false},
// {"sex", true, true, true, false, "select", false},
// {"delete", false, false, false, false, "", false},
//
// {"lat", false, true, true, false, "", false},
// {"lng", false, true, true, false, "", false},
// {"latitude", false, true, true, false, "", false},
// {"longitude", false, true, true, false, "", false},
//
// {"index", false, false, false, false, "index", false},
//
// {"password", false, true, false, false, "password", false},
// {"pwd", false, true, false, false, "password", false},
//
// {"version", false, false, false, false, "", false},
// {"seq", false, true, true, false, "", false},
// {"sort", false, true, true, false, "", false},
// {"note", false, true, true, false, "", false},
// {"description", false, true, true, false, "", false},
// {"abstract", false, true, true, false, "", false},
// {"content", false, true, true, false, "textArea", false},
// {"address", true, true, true, false, "", false},
// {"full_name", false, true, true, false, "", false},
// {"create_time", false, false, true, false, "time", true},
// {"modify_time", true, false, true, false, "time", true},
// {"image", false, true, true, false, "image", false},
// {"img", false, true, true, false, "image", false},
// {"icon", false, true, true, false, "image", false},
// {"avatar", false, true, true, false, "image", false},
// {"file", false, true, true, false, "file", false},
// {"age", false, true, true, false, "", false},
// {"email", false, true, true, false, "", false},
// {"time", true, true, true, false, "time", false},
// {"level", false, false, true, false, "", false},
// {"rule", true, true, true, false, "form", false},
// {"auth", false, true, true, false, "auth", true},
// {"table", true, false, true, false, "table", false},
// {"table_id", true, false, true, false, "table_id", false},
//}
+1277
View File
File diff suppressed because it is too large Load Diff
+236
View File
@@ -0,0 +1,236 @@
package code
var InitTpt = `package {{name}}
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
)
var ID = "{{id}}"
// Project 管理端项目
var Project = Proj{
//"user": UserCtr,
{{tablesCtr}}
"hotime":Ctr{
"login": func(that *Context) {
name := that.Req.FormValue("name")
password := that.Req.FormValue("password")
if name == "" || password == "" {
that.Display(3, "参数不足")
return
}
user := that.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
if user == nil {
that.Display(5, "登录失败")
return
}
that.Session("admin_id", user.GetCeilInt("id"))
that.Session("admin_name", name)
that.Display(0, that.SessionId)
},
"logout": func(that *Context) {
that.Session("admin_id", nil)
that.Session("admin_name", nil)
that.Display(0, "退出登录成功")
},
"info": func(that *Context) {
hotimeName := that.RouterString[0]
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
str, inData := that.MakeCodeRouter[hotimeName].Info("admin", data, that.Db)
where := Map{"id": that.Session("admin_id").ToCeilInt()}
if len(inData) ==1 {
inData["id"] =where["id"]
where = Map{"AND": inData}
}else if len(inData) >1 {
where["OR"]=inData
where = Map{"AND": where}
}
re := that.Db.Get("admin", str, where)
if re == nil {
that.Display(4, "找不到对应信息")
return
}
for k, v := range re {
column := that.MakeCodeRouter[hotimeName].TableColumns["admin"][k]
if column == nil {
continue
}
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
re[column.GetString("link")] = that.Db.Get(column.GetString("link"),"id," +column.GetString("value"), Map{"id": v})
}
}
that.Display(0, re)
},
},
}
`
var CtrTpt = `package {{name}}
import (
. "code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
"strings"
)
var {{table}}Ctr = Ctr{
"info": func(that *Context) {
hotimeName := that.RouterString[0]
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
str, inData := that.MakeCodeRouter[hotimeName].Info(that.RouterString[1], data, that.Db)
where := Map{"id": that.RouterString[2]}
if len(inData) ==1 {
inData["id"] =where["id"]
where = Map{"AND": inData}
}else if len(inData) >1 {
where["OR"]=inData
where = Map{"AND": where}
}
re := that.Db.Get(that.RouterString[1], str, where)
if re == nil {
that.Display(4, "找不到对应信息")
return
}
for k, v := range re {
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
if column == nil {
continue
}
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
re[column.GetString("link")] = that.Db.Get(column.GetString("link"), "id,"+column.GetString("value"), Map{"id": v})
}
}
that.Display(0, re)
},
"add": func(that *Context) {
hotimeName := that.RouterString[0]
inData := that.MakeCodeRouter[hotimeName].Add(that.RouterString[1], that.Req)
if inData == nil {
that.Display(3, "请求参数不足")
return
}
re := that.Db.Insert(that.RouterString[1], inData)
if re == 0 {
that.Display(4, "无法插入对应数据")
return
}
//索引管理,便于检索以及权限
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
inData["index"] = index.GetString("index")+ObjToStr(re)+","
that.Db.Update(that.RouterString[1],Map{"index":inData["index"]},Map{"id":re})
}else if inData.GetString("index") != ""{
inData["index"] = "," + ObjToStr(re) + ","
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
}
that.Display(0, re)
},
"update": func(that *Context) {
hotimeName := that.RouterString[0]
inData := that.MakeCodeRouter[hotimeName].Edit(that.RouterString[1], that.Req)
if inData == nil {
that.Display(3, "没有找到要更新的数据")
return
}
//索引管理,便于检索以及权限
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
Index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": that.RouterString[2]})
parentIndex := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
inData["index"] = parentIndex.GetString("index") + that.RouterString[2] + ","
childNodes := that.Db.Select(that.RouterString[1], "id,` + "`index`" + `", Map{"index[~]": "," + that.RouterString[2] + ","})
for _, v := range childNodes {
v["index"] = strings.Replace(v.GetString("index"), Index.GetString("index"), inData.GetString("index"), -1)
that.Db.Update(that.RouterString[1], Map{"index": v["index"]}, Map{"id": v.GetCeilInt("id")})
}
}
re := that.Db.Update(that.RouterString[1], inData, Map{"id": that.RouterString[2]})
if re == 0 {
that.Display(4, "更新数据失败")
return
}
that.Display(0, re)
},
"remove": func(that *Context) {
hotimeName := that.RouterString[0]
inData := that.MakeCodeRouter[hotimeName].Delete(that.RouterString[1], that.Req)
if inData == nil {
that.Display(3, "请求参数不足")
return
}
re :=int64(0)
//索引管理,便于检索以及权限
if inData.Get("parent_id") != nil && inData.GetSlice("index") != nil {
re=that.Db.Delete(that.RouterString[1], Map{"index[~]": "," + that.RouterString[2] + ","})
}else {
re=that.Db.Delete(that.RouterString[1], Map{"id": that.RouterString[2]})
}
if re == 0 {
that.Display(4, "删除数据失败")
return
}
that.Display(0, "删除成功")
},
"search": func(that *Context) {
hotimeName := that.RouterString[0]
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
columnStr, leftJoin, where := that.MakeCodeRouter[hotimeName].Search(that.RouterString[1], data, that.Req, that.Db)
page := ObjToInt(that.Req.FormValue("page"))
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
if page < 1 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
count := that.Db.Count(that.RouterString[1], leftJoin, where)
reData := that.Db.Page(page, pageSize).
PageSelect(that.RouterString[1], leftJoin, columnStr, where)
for _, v := range reData {
for k, _ := range v {
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
if column == nil {
continue
}
if column["list"] != false && column["name"] == "parent_id" && column.GetString("link") != "" {
parentC := that.Db.Get(column.GetString("link"), column.GetString("value"), Map{"id": v.GetCeilInt(k)})
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = ""
if parentC != nil {
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = parentC.GetString(column.GetString("value"))
}
}
}
}
that.Display(0, Map{"count": count, "data": reData})
},
}
`
+18
View File
@@ -0,0 +1,18 @@
package common
import (
"time"
)
type ContextBase struct {
tag string
}
//唯一标志
func (that *ContextBase) GetTag() string {
if that.tag == "" {
that.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
}
return that.tag
}
+27
View File
@@ -0,0 +1,27 @@
package common
import (
"github.com/sirupsen/logrus"
)
// Error 框架层处理错误
type Error struct {
Logger *logrus.Logger
error
}
func (that *Error) GetError() error {
return that.error
}
func (that *Error) SetError(err error) {
that.error = err
if that.Logger != nil && err != nil {
//that.Logger=log.GetLog("",false)
that.Logger.Warn(err)
}
return
}
+81 -89
View File
@@ -1,13 +1,9 @@
package hotime
package common
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
@@ -29,83 +25,7 @@ import (
// return res
//}
func LogError(logMsg interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_ERROR)
}
func LogWarn(logMsg interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_WARN)
}
func LogInfo(logMsg ...interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_INFO)
}
//func LogFmt(logMsg interface{}, loglevel ...LOG_MODE) {
//
// logFmt(logMsg, 2, loglevel...)
//}
//日志打印以及存储到文件
func logFmt(logMsg interface{}, printLevel int, loglevel ...LOG_MODE) {
if Config.GetInt("logLevel") == int(LOG_NIL) {
return
}
lev := LOG_INFO
if len(loglevel) != 0 {
lev = loglevel[0]
}
gofile := ""
if Config.GetInt("debug") != 0 && printLevel != 0 {
_, file, line, ok := runtime.Caller(printLevel)
if ok {
gofile = " " + file + ":" + ObjToStr(line)
}
}
logStr := ""
if lev == LOG_INFO {
logStr = "info:"
}
if printLevel == 0 {
logStr = "connect:"
}
if lev == LOG_WARN {
logStr = "warn:"
}
if lev == LOG_ERROR {
logStr = "error:"
}
logStr = fmt.Sprintln(time.Now().Format("2006/01/02 15:04:05"), logStr, logMsg, gofile)
//打印日志
fmt.Print(logStr)
//不需要存储到文件
if Config.GetString("logFile") == "" {
return
}
//存储到文件
logFilePath := time.Now().Format(Config.GetString("logFile"))
os.MkdirAll(filepath.Dir(logFilePath), os.ModeAppend)
//os.Create(logFilePath)
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return
}
f.Write([]byte(logStr))
f.Close()
}
//字符串首字符大写
// StrFirstToUpper 字符串首字符大写
func StrFirstToUpper(str string) string {
if len(str) == 0 {
return str
@@ -117,7 +37,79 @@ func StrFirstToUpper(str string) string {
return strings.ToUpper(first) + other
}
//字符串截取
// 时间转字符串,第二个参数支持1-5对应显示年月日时分秒
func Time2Str(t time.Time, qu ...interface{}) string {
if t.Unix() < 0 {
return ""
}
tp := 5
if len(qu) != 0 {
tp = (qu[0]).(int)
}
switch tp {
case 1:
return t.Format("2006-01")
case 2:
return t.Format("2006-01-02")
case 3:
return t.Format("2006-01-02 15")
case 4:
return t.Format("2006-01-02 15:04")
case 5:
return t.Format("2006-01-02 15:04:05")
case 12:
return t.Format("01-02")
case 14:
return t.Format("01-02 15:04")
case 15:
return t.Format("01-02 15:04:05")
case 34:
return t.Format("15:04")
case 35:
return t.Format("15:04:05")
}
return t.Format("2006-01-02 15:04:05")
}
// StrLd 相似度计算 ld compares two strings and returns the levenshtein distance between them.
func StrLd(s, t string, ignoreCase bool) int {
if ignoreCase {
s = strings.ToLower(s)
t = strings.ToLower(t)
}
d := make([][]int, len(s)+1)
for i := range d {
d[i] = make([]int, len(t)+1)
}
for i := range d {
d[i][0] = i
}
for j := range d[0] {
d[0][j] = j
}
for j := 1; j <= len(t); j++ {
for i := 1; i <= len(s); i++ {
if s[i-1] == t[j-1] {
d[i][j] = d[i-1][j-1]
} else {
min := d[i-1][j]
if d[i][j-1] < min {
min = d[i][j-1]
}
if d[i-1][j-1] < min {
min = d[i-1][j-1]
}
d[i][j] = min + 1
}
}
}
return d[len(s)][len(t)]
}
// Substr 字符串截取
func Substr(str string, start int, length int) string {
rs := []rune(str)
rl := len(rs)
@@ -148,7 +140,7 @@ func Substr(str string, start int, length int) string {
return string(rs[start:end])
}
//获取最后出现字符串的下标
// IndexLastStr 获取最后出现字符串的下标
//return 找不到返回 -1
func IndexLastStr(str, sep string) int {
sepSlice := []rune(sep)
@@ -179,7 +171,7 @@ func IndexLastStr(str, sep string) int {
return -1
}
//md5
// Md5 md5
func Md5(req string) string {
md5Ctx := md5.New()
md5Ctx.Write([]byte(req))
@@ -187,7 +179,7 @@ func Md5(req string) string {
return hex.EncodeToString(cipherStr)
}
//随机数
// Rand 随机数
func Rand(count int) int {
res := Random()
for i := 0; i < count; i++ {
@@ -212,7 +204,7 @@ func Random() float64 {
}
//随机数范围
// RandX 随机数范围
func RandX(small int, max int) int {
res := 0
//随机对象
@@ -264,7 +256,7 @@ func RandX(small int, max int) int {
// GetDb()
//}
//复制返回数组
// DeepCopyMap 复制返回数组
func DeepCopyMap(value interface{}) interface{} {
if valueMap, ok := value.(Map); ok {
newMap := make(Map)
@@ -323,7 +315,7 @@ func DeepCopyMap(value interface{}) interface{} {
// }
//}
//浮点数四舍五入保留小数
// Round 浮点数四舍五入保留小数
func Round(f float64, n int) float64 {
pow10_n := math.Pow10(n)
return math.Trunc((f+0.5/pow10_n)*pow10_n) / pow10_n
+189
View File
@@ -0,0 +1,189 @@
package common
import (
"encoding/json"
"errors"
"reflect"
"sort"
"time"
)
//hotime的常用map
type Map map[string]interface{}
//获取string
func (that Map) GetString(key string, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((that)[key])
}
func (that *Map) Pointer() *Map {
return that
}
//增加接口
func (that Map) Put(key string, value interface{}) {
//if that==nil{
// that=Map{}
//}
that[key] = value
}
//删除接口
func (that Map) Delete(key string) {
delete(that, key)
}
//获取Int
func (that Map) GetInt(key string, err ...*Error) int {
v := ObjToInt((that)[key], err...)
return v
}
//获取Int
func (that Map) GetInt64(key string, err ...*Error) int64 {
v := ObjToInt64((that)[key], err...)
return v
}
//获取向上取整Int64
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
v := ObjToCeilInt64((that)[key], err...)
return v
}
//获取向上取整Int
func (that Map) GetCeilInt(key string, err ...*Error) int {
v := ObjToCeilInt((that)[key], err...)
return v
}
//获取向上取整float64
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
v := ObjToCeilFloat64((that)[key], err...)
return v
}
//获取Float64
func (that Map) GetFloat64(key string, err ...*Error) float64 {
v := ObjToFloat64((that)[key], err...)
return v
}
func (that Map) GetSlice(key string, err ...*Error) Slice {
//var v Slice
v := ObjToSlice((that)[key], err...)
return v
}
func (that Map) GetBool(key string, err ...*Error) bool {
//var v Slice
v := ObjToBool((that)[key], err...)
return v
}
func (that Map) GetTime(key string, err ...*Error) time.Time {
v := ObjToTime((that)[key], err...)
return v
}
func (that Map) RangeSort(callback func(k string, v interface{}) (isEnd bool)) {
testQu := []string{}
//testQuData:= qu[0].(Map)
for key, _ := range that {
//fmt.Println(key, ":", value)
testQu = append(testQu, key)
}
sort.Strings(testQu)
for _, k := range testQu {
re := callback(k, that[k])
if re {
return
}
}
}
func (that Map) GetMap(key string, err ...*Error) Map {
//var data Slice
v := ObjToMap((that)[key], err...)
return v
}
func (that Map) Get(key string, err ...*Error) interface{} {
if v, ok := (that)[key]; ok {
return v
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
//请传递指针过来
func (that Map) ToStruct(stct interface{}) {
data := reflect.ValueOf(stct).Elem()
for k, v := range that {
ks := StrFirstToUpper(k)
dkey := data.FieldByName(ks)
if !dkey.IsValid() {
continue
}
switch dkey.Type().String() {
case "int":
dkey.SetInt(that.GetInt64(k))
case "int64":
dkey.Set(reflect.ValueOf(that.GetInt64(k)))
case "float64":
dkey.Set(reflect.ValueOf(that.GetFloat64(k)))
case "string":
dkey.Set(reflect.ValueOf(that.GetString(k)))
case "interface{}":
dkey.Set(reflect.ValueOf(v))
}
}
}
func (that Map) ToJsonString() string {
return ObjToStr(that)
}
func (that Map) JsonToMap(jsonStr string, err ...*Error) {
e := json.Unmarshal([]byte(jsonStr), &that)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
}
+103
View File
@@ -0,0 +1,103 @@
package common
import "time"
//对象封装方便取用
type Obj struct {
Data interface{}
Error
ContextBase
}
func (that *Obj) Put(data interface{}) {
that.Data = data
}
func (that *Obj) ToInt(err ...Error) int {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToInt(that.Data, &that.Error)
}
func (that *Obj) ToTime(err ...Error) time.Time {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToTime(that.Data, &that.Error)
}
func (that *Obj) ToInt64(err ...Error) int64 {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToInt64(that.Data, &that.Error)
}
func (that *Obj) ToFloat64(err ...Error) float64 {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToFloat64(that.Data, &that.Error)
}
// ToCeilFloat64 获取向上取整float64
func (that *Obj) ToCeilFloat64(err ...*Error) float64 {
if len(err) != 0 {
that.Error = *err[0]
}
v := ObjToCeilFloat64(that.Data, err...)
return v
}
func (that *Obj) ToStr() string {
return ObjToStr(that.Data)
}
func (that *Obj) ToMap(err ...Error) Map {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToMap(that.Data, &that.Error)
}
func (that *Obj) ToSlice(err ...Error) Slice {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToSlice(that.Data, &that.Error)
}
func (that *Obj) ToMapArray(err ...Error) []Map {
if len(err) != 0 {
that.Error = err[0]
}
return ObjToMapArray(that.Data, &that.Error)
}
func (that *Obj) ToObj() interface{} {
return that.Data
}
//获取向上取整Int64
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
if len(err) != 0 {
that.Error = *err[0]
}
v := ObjToCeilInt64(that.Data, err...)
return v
}
//获取向上取整Int
func (that *Obj) ToCeilInt(err ...*Error) int {
if len(err) != 0 {
that.Error = *err[0]
}
v := ObjToCeilInt(that.Data, err...)
return v
}
+100 -2
View File
@@ -1,10 +1,11 @@
package hotime
package common
import (
"encoding/json"
"errors"
"math"
"strconv"
"time"
)
//仅限于hotime.Slice
@@ -96,6 +97,68 @@ func ObjToSlice(obj interface{}, e ...*Error) Slice {
return v
}
func ObjToTime(obj interface{}, e ...*Error) time.Time {
tInt := ObjToInt64(obj)
//字符串类型,只支持标准mysql datetime格式
if tInt == 0 {
tStr := ObjToStr(obj)
if len(tStr) > 18 {
t, e := time.Parse("2006-01-02 15:04:05", tStr)
if e == nil {
return t
}
} else if len(tStr) > 15 {
t, e := time.Parse("2006-01-02 15:04", tStr)
if e == nil {
return t
}
} else if len(tStr) > 12 {
t, e := time.Parse("2006-01-02 15", tStr)
if e == nil {
return t
}
} else if len(tStr) > 9 {
t, e := time.Parse("2006-01-02", tStr)
if e == nil {
return t
}
} else if len(tStr) > 6 {
t, e := time.Parse("2006-01", tStr)
if e == nil {
return t
}
}
}
//纳秒级别
if len(ObjToStr(tInt)) > 16 {
t := time.Time{}.Add(time.Nanosecond * time.Duration(tInt))
return t
//微秒级别
} else if len(ObjToStr(tInt)) > 13 {
t := time.Time{}.Add(time.Microsecond * time.Duration(tInt))
return t
//毫秒级别
} else if len(ObjToStr(tInt)) > 10 {
t := time.Time{}.Add(time.Millisecond * time.Duration(tInt))
return t
//秒级别
} else if len(ObjToStr(tInt)) > 9 {
t := time.Time{}.Add(time.Second * time.Duration(tInt))
return t
} else if len(ObjToStr(tInt)) > 3 {
t, e := time.Parse("2006", ObjToStr(tInt))
if e == nil {
return t
}
}
return time.Time{}
}
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
var err error
v := float64(0)
@@ -135,6 +198,15 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
err = errors.New("没有合适的转换对象!")
}
}
if math.IsNaN(v) {
err = errors.New("float64 is NaN")
v = 0
}
if math.IsInf(v, 0) {
err = errors.New("float64 is Inf")
v = 0
}
if len(e) != 0 {
e[0].SetError(err)
}
@@ -212,6 +284,31 @@ func ObjToInt(obj interface{}, e ...*Error) int {
return int(v)
}
func ObjToBool(obj interface{}, e ...*Error) bool {
var err error
v := false
if obj == nil {
err = errors.New("没有合适的转换对象!")
} else {
switch obj.(type) {
case bool:
v = obj.(bool)
default:
toInt := ObjToInt(obj)
if toInt != 0 {
v = true
}
err = errors.New("没有合适的转换对象!")
}
}
if len(e) != 0 {
e[0].SetError(err)
}
return v
}
func ObjToStr(obj interface{}) string {
// fmt.Println(reflect.ValueOf(obj).Type().String() )
str := ""
@@ -232,7 +329,8 @@ func ObjToStr(obj interface{}) string {
case float64:
str = strconv.FormatFloat(obj.(float64), 'f', -1, 64)
default:
strbte, err := json.Marshal(obj)
strbte, err := json.MarshalIndent(obj, "", "\t")
if err == nil {
str = string(strbte)
}
+104
View File
@@ -0,0 +1,104 @@
package common
import (
"errors"
"time"
)
type Slice []interface{}
// GetString 获取string
func (that Slice) GetString(key int, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((that)[key])
}
func (that Slice) GetTime(key int, err ...*Error) time.Time {
v := ObjToTime((that)[key], err...)
return v
}
// GetInt 获取Int
func (that Slice) GetInt(key int, err ...*Error) int {
v := ObjToInt((that)[key], err...)
return v
}
// GetInt64 获取Int
func (that Slice) GetInt64(key int, err ...*Error) int64 {
v := ObjToInt64((that)[key], err...)
return v
}
// GetCeilInt64 获取向上取整Int64
func (that Slice) GetCeilInt64(key int, err ...*Error) int64 {
v := ObjToCeilInt64((that)[key], err...)
return v
}
// GetCeilInt 获取向上取整Int
func (that Slice) GetCeilInt(key int, err ...*Error) int {
v := ObjToCeilInt((that)[key], err...)
return v
}
// GetCeilFloat64 获取向上取整float64
func (that Slice) GetCeilFloat64(key int, err ...*Error) float64 {
v := ObjToCeilFloat64((that)[key], err...)
return v
}
// GetFloat64 获取Float64
func (that Slice) GetFloat64(key int, err ...*Error) float64 {
v := ObjToFloat64((that)[key], err...)
return v
}
func (that Slice) GetSlice(key int, err ...*Error) Slice {
v := ObjToSlice((that)[key], err...)
return v
}
func (that Slice) GetBool(key int, err ...*Error) bool {
//var v Slice
v := ObjToBool((that)[key], err...)
return v
}
func (that Slice) GetMap(key int, err ...*Error) Map {
//var v Map
v := ObjToMap((that)[key], err...)
return v
}
func (that Slice) Get(key int, err ...*Error) interface{} {
if key < len(that) {
return that[key]
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
func (that Slice) Put(key int, value interface{}) {
that[key] = value
}
func (that Slice) ToJsonString() string {
return ObjToStr(that)
}
+1 -13
View File
@@ -1,16 +1,4 @@
package hotime
type LOG_MODE int
const (
LOG_NIL = 0
LOG_FMT = 1
//LOG_FILE = 2
LOG_INFO LOG_MODE = 0
LOG_WARN LOG_MODE = 1
LOG_ERROR LOG_MODE = 2
)
//session储存头
// HEAD_SESSION_ADD session储存头
const HEAD_SESSION_ADD = "session#"
+59 -20
View File
@@ -1,65 +1,104 @@
package hotime
import (
. "code.hoteas.com/golang/hotime/common"
. "code.hoteas.com/golang/hotime/db"
"encoding/json"
"errors"
"net/http"
"strings"
"time"
)
type Context struct {
contextBase
*Application
Resp http.ResponseWriter
Req *http.Request
Application *Application
Log Map //日志有则创建
RouterString []string
Config Map
Db *HoTimeDB
RespData Map
CacheIns
RespFunc func()
//CacheIns
SessionIns
DataSize int
HandlerStr string //复写请求url
}
//唯一标志
func (this *Context) Mtd(router [3]string) Map {
this.Application.Router[router[0]][router[1]][router[2]](this)
d := this.RespData
this.RespData = nil
// Mtd 唯一标志
func (that *Context) Mtd(router [3]string) Map {
that.Application.Router[router[0]][router[1]][router[2]](that)
d := that.RespData
that.RespData = nil
return d
}
//打印
func (this *Context) Display(statu int, data interface{}) {
func (that *Context) Display(statu int, data interface{}) {
resp := Map{"statu": statu}
resp := Map{"status": statu}
if statu != 0 {
temp := Map{}
tpe := this.Config.GetMap("error").GetString(ObjToStr(statu))
tpe := that.Config.GetMap("error").GetString(ObjToStr(statu))
if tpe == "" {
logFmt(errors.New("找不到对应的错误码"), 2, LOG_WARN)
//logFmt(errors.New("找不到对应的错误码"), 2, LOG_WARN)
}
temp["type"] = tpe
temp["msg"] = data
resp["result"] = temp
//兼容android等需要json转对象的服务
resp["error"] = temp
} else {
resp["result"] = data
}
this.RespData = resp
that.RespData = resp
//this.Data=d;
//that.Data=d;
}
func (this *Context) View() {
if this.RespData == nil {
func (that *Context) View() {
if that.RespFunc != nil {
that.RespFunc()
}
if that.RespData == nil {
return
}
d, err := json.Marshal(this.RespData)
//创建日志
if that.Log != nil {
that.Log["time"] = time.Now().Format("2006-01-02 15:04")
if that.Session("admin_id").Data != nil {
that.Log["admin_id"] = that.Session("admin_id").ToCeilInt()
}
if that.Session("user_id").Data != nil {
that.Log["user_id"] = that.Session("user_id").ToCeilInt()
}
//负载均衡优化
ipStr := ""
if that.Req.Header.Get("X-Forwarded-For") != "" {
ipStr = that.Req.Header.Get("X-Forwarded-For")
} else if that.Req.Header.Get("X-Real-IP") != "" {
ipStr = that.Req.Header.Get("X-Real-IP")
}
//负载均衡优化
if ipStr == "" {
//RemoteAddr := that.Req.RemoteAddr
ipStr = Substr(that.Req.RemoteAddr, 0, strings.Index(that.Req.RemoteAddr, ":"))
}
that.Log["ip"] = ipStr
that.Db.Insert("logs", that.Log)
}
d, err := json.Marshal(that.RespData)
if err != nil {
that.Display(1, err.Error())
that.View()
return
}
this.RespData = nil
this.Resp.Write(d)
that.DataSize = len(d)
that.RespData = nil
that.Resp.Write(d)
return
}
-17
View File
@@ -1,17 +0,0 @@
package hotime
import "time"
type contextBase struct {
Error
tag int64
}
//唯一标志
func (this *contextBase) GetTag() int64 {
if this.tag == int64(0) {
this.tag = time.Now().UnixNano()
}
return this.tag
}
-935
View File
@@ -1,935 +0,0 @@
package hotime
import (
"database/sql"
"encoding/json"
"errors"
"os"
"reflect"
"strings"
)
type HoTimeDB struct {
*sql.DB
contextBase
CacheIns
Type string
DBCached bool
LastQuery string
LastData []interface{}
ConnectFunc func(err ...*Error) *sql.DB
LastErr Error
limit Slice
Tx *sql.Tx //事务对象
}
//设置数据库配置连接
func (this *HoTimeDB) SetConnect(connect func(err ...*Error) *sql.DB, err ...*Error) {
this.ConnectFunc = connect
this.InitDb()
}
//事务,如果action返回true则执行成功;false则回滚
func (this *HoTimeDB) Action(action func(db HoTimeDB) bool) bool {
db := HoTimeDB{DB: this.DB, CacheIns: this.CacheIns, DBCached: this.DBCached}
tx, err := db.Begin()
if err != nil {
this.LastErr.SetError(err)
return false
}
db.Tx = tx
result := action(db)
if !result {
db.Tx.Rollback()
return result
}
db.Tx.Commit()
return result
}
func (this *HoTimeDB) InitDb(err ...*Error) Error {
if len(err) != 0 {
this.LastErr = *(err[0])
}
this.DB = this.ConnectFunc(&this.LastErr)
if this.DB == nil {
return this.LastErr
}
e := this.DB.Ping()
this.LastErr.SetError(e)
return this.LastErr
}
func (this *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
page = (page - 1) * pageRow
if page < 0 {
page = 1
}
this.limit = Slice{page, pageRow}
return this
}
func (this *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
if len(qu) == 1 {
qu = append(qu, Map{"LIMIT": this.limit})
}
if len(qu) == 2 {
temp := qu[1].(Map)
temp["LIMIT"] = this.limit
qu[1] = temp
}
if len(qu) == 3 {
temp := qu[2].(Map)
temp["LIMIT"] = this.limit
qu[2] = temp
}
//fmt.Println(qu)
data := this.Select(table, qu...)
return data
}
//数据库数据解析
func (this *HoTimeDB) Row(resl *sql.Rows) []Map {
dest := make([]Map, 0)
strs, _ := resl.Columns()
for i := 0; resl.Next(); i++ {
lis := make(Map, 0)
a := make([]interface{}, len(strs))
b := make([]interface{}, len(a))
for j := 0; j < len(a); j++ {
b[j] = &a[j]
}
resl.Scan(b...)
for j := 0; j < len(a); j++ {
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
lis[strs[j]] = string(a[j].([]byte))
} else {
lis[strs[j]] = a[j]
}
}
//防止int被误读为float64
jlis, e := json.Marshal(lis)
if e != nil {
this.LastErr.SetError(e)
} else {
lis.JsonToMap(string(jlis), &this.LastErr)
}
dest = append(dest, lis)
}
return dest
}
//
////code=0,1,2 0 backup all,1 backup data,2 backup ddl
//func (this *HoTimeDB) Backup(path string, code int) {
// var cmd *exec.Cmd
// switch code {
// case 0:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
// case 1:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]), ObjToStr(Config["dbName"]))
// case 2:cmd= exec.Command("mysqldump","--no-data","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
// }
//
//
//
// stdout, err := cmd.StdoutPipe()
// if err != nil {
// log.Println(err)
// }
//
// if err := cmd.Start(); err != nil {
// log.Println(err)
// }
//
// bytes, err := ioutil.ReadAll(stdout)
// if err != nil {
// log.Println(err)
// }
// err = ioutil.WriteFile(path, bytes, 0644)
// if err != nil {
// panic(err)
// }
// return ;
// //
// //db := ``
// //fmt.Println(db)
// //
// //tables := this.Query("show tables")
// //lth := len(tables)
// //if lth == 0 {
// // return
// //}
// //for k, _ := range tables[0] {
// // db = Substr(k, 10, len(k))
// //}
// //
// //fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
// //fd.Write([]byte("/*datetime " + time.Now().Format("2006-01-02 15:04:05") + " */ \r\n"))
// //fd.Close()
// //
// //for i := 0; i < lth; i++ {
// // tt := tables[i]["Tables_in_"+db].(string)
// // this.backupSave(path, tt, code)
// // debug.FreeOSMemory()
// //}
//
//}
func (this *HoTimeDB) backupSave(path string, tt string, code int) {
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
defer fd.Close()
str := "\r\n"
if code == 0 || code == 2 {
str += this.backupDdl(tt)
}
if code == 0 || code == 1 {
str += "insert into `" + tt + "`\r\n\r\n("
str += this.backupCol(tt)
}
fd.Write([]byte(str))
}
func (this *HoTimeDB) backupDdl(tt string) string {
data := this.Query("show create table " + tt)
if len(data) == 0 {
return ""
}
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
}
func (this *HoTimeDB) backupCol(tt string) string {
str := ""
data := this.Select(tt, "*")
lthData := len(data)
if lthData == 0 {
return str
}
lthCol := len(data[0])
col := make([]string, lthCol)
tempLthData := 0
for k, _ := range data[0] {
if tempLthData == lthCol-1 {
str += "`" + k + "`)"
} else {
str += "`" + k + "`,"
}
col[tempLthData] = k
tempLthData++
}
str += " values"
for j := 0; j < lthData; j++ {
for m := 0; m < lthCol; m++ {
if m == 0 {
str += "("
}
v := "NULL"
if data[j][col[m]] != nil {
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
}
if m == lthCol-1 {
str += v + ")"
} else {
str += v + ","
}
}
if j == lthData-1 {
str += ";\r\n\r\n"
} else {
str += ",\r\n\r\n"
}
}
return str
}
func (this *HoTimeDB) md5(query string, args ...interface{}) string {
strByte, _ := json.Marshal(args)
str := Md5(query + ":" + string(strByte))
return str
}
func (this *HoTimeDB) Query(query string, args ...interface{}) []Map {
//fmt.Println(query)
var err error
var resl *sql.Rows
this.LastQuery = query
this.LastData = args
if this.DB == nil {
err = errors.New("没有初始化数据库")
this.LastErr.SetError(err)
return nil
}
if this.Tx != nil {
resl, err = this.Tx.Query(query, args...)
} else {
resl, err = this.DB.Query(query, args...)
}
this.LastErr.SetError(err)
if err != nil {
if err = this.DB.Ping(); err != nil {
this.LastErr.SetError(err)
this.InitDb()
if this.LastErr.GetError() != nil {
return nil
}
return this.Query(query, args...)
}
return nil
}
return this.Row(resl)
}
func (this *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, Error) {
this.LastQuery = query
this.LastData = args
var e error
var resl sql.Result
if this.DB == nil {
err := errors.New("没有初始化数据库")
this.LastErr.SetError(err)
return nil, this.LastErr
}
if this.Tx != nil {
resl, e = this.Tx.Exec(query, args...)
} else {
resl, e = this.DB.Exec(query, args...)
}
this.LastErr.SetError(e)
//判断是否连接断开了
if e != nil {
if e = this.DB.Ping(); e != nil {
this.LastErr.SetError(e)
this.InitDb()
if this.LastErr.GetError() != nil {
return resl, this.LastErr
}
return this.Exec(query, args...)
}
}
return resl, this.LastErr
}
//func (this *HoTimeDB)copy(data []Map)[]Map{
// if data==nil{
// return nil
// }
//
// lth:=len(data)
//
// res:=make([]Map,lth)
//
//
// for i:=0;i<lth;i++{
//
// res[i]=DeepCopy(data[i]).(Map)
// }
//
// return res
//
//}
func (this *HoTimeDB) Select(table string, qu ...interface{}) []Map {
query := "SELECT"
where := Map{}
qs := make([]interface{}, 0)
intQs, intWhere := 0, 1
join := false
if len(qu) == 3 {
intQs = 1
intWhere = 2
join = true
}
if len(qu) > 0 {
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
query += " " + qu[intQs].(string)
} else {
for i := 0; i < len(qu[intQs].(Slice)); i++ {
k := qu[intQs].(Slice)[i].(string)
if strings.Contains(k, " AS ") {
query += " " + k + " "
} else {
query += " `" + k + "` "
}
if i+1 != len(qu[intQs].(Slice)) {
query = query + ", "
}
}
}
} else {
query += " *"
}
query += " FROM " + table
if join {
for k, v := range qu[0].(Map) {
switch Substr(k, 0, 3) {
case "[>]":
query += " LEFT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
case "[<]":
query += " RIGHT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
}
switch Substr(k, 0, 4) {
case "[<>]":
query += " FULL JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
case "[><]":
query += " INNER JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
}
}
}
if len(qu) > 1 {
where = qu[intWhere].(Map)
}
temp, resWhere := this.where(where)
query += temp
qs = append(qs, resWhere...)
md5 := this.md5(query, qs...)
if this.DBCached && this.CacheIns != nil {
//如果缓存有则从缓存取
cacheData := this.Cache(table + ":" + md5)
if cacheData.Data != nil {
return cacheData.ToMapArray()
}
}
//无缓存则数据库取
res := this.Query(query, qs...)
if res == nil {
res = []Map{}
}
//缓存
if this.DBCached && this.CacheIns != nil {
this.Cache(table+":"+md5, res)
}
return res
}
func (this *HoTimeDB) Get(table string, qu ...interface{}) Map {
//fmt.Println(qu)
if len(qu) == 1 {
qu = append(qu, Map{"LIMIT": 1})
}
if len(qu) == 2 {
temp := qu[1].(Map)
temp["LIMIT"] = 1
qu[1] = temp
}
if len(qu) == 3 {
temp := qu[2].(Map)
temp["LIMIT"] = 1
qu[2] = temp
}
//fmt.Println(qu)
data := this.Select(table, qu...)
if len(data) == 0 {
return nil
}
return data[0]
}
/**
** 计数
*/
func (this *HoTimeDB) Count(table string, qu ...interface{}) int {
req := []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "COUNT(*)")
req = append(req, qu[1])
} else {
req = append(req, "COUNT(*)")
req = append(req, qu...)
}
//req=append(req,qu...)
data := this.Select(table, req...)
//fmt.Println(data)
if len(data) == 0 {
return 0
}
//res,_:=StrToInt(data[0]["COUNT(*)"].(string))
res := ObjToStr(data[0]["COUNT(*)"])
count, _ := StrToInt(res)
return count
}
var condition = []string{"AND", "OR"}
var vcond = []string{"GROUP", "ORDER", "LIMIT"}
//where语句解析
func (this *HoTimeDB) where(data Map) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
//AND OR判断
for k, v := range data {
x := 0
for i := 0; i < len(condition); i++ {
if condition[i] == k {
tw, ts := this.cond(k, v.(Map))
where += tw
res = append(res, ts...)
break
}
x++
}
y := 0
for j := 0; j < len(vcond); j++ {
if vcond[j] == k {
break
}
y++
}
if x == len(condition) && y == len(vcond) {
tv, vv := this.varCond(k, v)
where += tv
res = append(res, vv...)
}
}
if len(where) != 0 {
where = " WHERE " + where
}
//特殊字符
for j := 0; j < len(vcond); j++ {
for k, v := range data {
if vcond[j] == k {
if k == "ORDER" {
where += " " + k + " BY "
//fmt.Println(reflect.ValueOf(v).Type())
//break
} else if k == "GROUP" {
where += " " + k + " BY "
} else {
where += " " + k
}
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
for i := 0; i < len(v.(Slice)); i++ {
where += " " + ObjToStr(v.(Slice)[i])
if len(v.(Slice)) != i+1 {
where += ","
}
}
} else {
//fmt.Println(v)
where += " " + ObjToStr(v)
}
break
}
}
}
return where, res
}
func (this *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
length := len(k)
if length > 4 {
def := false
switch Substr(k, length-3, 3) {
case "[>]":
k = strings.Replace(k, "[>]", "", -1)
where += "`" + k + "`>? "
res = append(res, v)
case "[<]":
k = strings.Replace(k, "[<]", "", -1)
where += "`" + k + "`<? "
res = append(res, v)
case "[!]":
k = strings.Replace(k, "[!]", "", -1)
where, res = this.notIn(k, v, where, res)
case "[#]":
k = strings.Replace(k, "[#]", "", -1)
where += " " + k + "=" + ObjToStr(v)
case "[#!]":
k = strings.Replace(k, "[#!]", "", -1)
where += " " + k + "!=" + ObjToStr(v)
case "[!#]":
k = strings.Replace(k, "[!#]", "", -1)
where += " " + k + "!=" + ObjToStr(v)
case "[~]":
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = "%" + ObjToStr(v) + "%"
res = append(res, v)
case "[!~]": //左边任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = "%" + ObjToStr(v)
res = append(res, v)
case "[~!]": //右边任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = ObjToStr(v) + "%"
res = append(res, v)
case "[~~]": //手动任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
//v = ObjToStr(v)
res = append(res, v)
default:
def = true
}
if def {
switch Substr(k, length-4, 4) {
case "[>=]":
k = strings.Replace(k, "[>=]", "", -1)
where += "`" + k + "`>=? "
res = append(res, v)
case "[<=]":
k = strings.Replace(k, "[<=]", "", -1)
where += "`" + k + "`<=? "
res = append(res, v)
case "[><]":
k = strings.Replace(k, "[><]", "", -1)
where += "`" + k + "` NOT BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
res = append(res, v.(Slice)[1])
case "[<>]":
k = strings.Replace(k, "[<>]", "", -1)
where += "`" + k + "` BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
res = append(res, v.(Slice)[1])
default:
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
where += "`" + k + "` IN ("
res = append(res, (v.(Slice))...)
if len(v.(Slice)) == 0 {
where += ") "
} else {
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
}
} else {
where += "`" + k + "`=? "
res = append(res, v)
}
}
}
} else if k == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
where += " " + ObjToStr(v) + " "
} else {
//fmt.Println(reflect.ValueOf(v).Type().String())
if v == nil {
where += "`" + k + "` IS NULL"
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
//fmt.Println(v)
where += "`" + k + "` IN ("
res = append(res, (v.(Slice))...)
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
where += "`" + k + "` IN ("
res = append(res, (v.([]interface{}))...)
for i := 0; i < len(v.([]interface{})); i++ {
if i+1 != len(v.([]interface{})) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += "`" + k + "`=? "
res = append(res, v)
}
}
return where, res
}
// this.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1})
func (this *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
//where:=""
//fmt.Println(reflect.ValueOf(v).Type().String())
if v == nil {
where += "`" + k + "` IS NOT NULL "
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
where += "`" + k + "` NOT IN ("
res = append(res, (v.(Slice))...)
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
where += "`" + k + "` NOT IN ("
res = append(res, (v.([]interface{}))...)
for i := 0; i < len(v.([]interface{})); i++ {
if i+1 != len(v.([]interface{})) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += "`" + k + "` !=? "
res = append(res, v)
}
return where, res
}
func (this *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
where := " "
res := make([]interface{}, 0)
lens := len(data)
//fmt.Println(lens)
for k, v := range data {
x := 0
for i := 0; i < len(condition); i++ {
if condition[i] == k {
tw, ts := this.cond(k, v.(Map))
if lens--; lens <= 0 {
//fmt.Println(lens)
where += "(" + tw + ") "
} else {
where += "(" + tw + ") " + tag + " "
}
res = append(res, ts...)
break
}
x++
}
if x == len(condition) {
tv, vv := this.varCond(k, v)
res = append(res, vv...)
if lens--; lens <= 0 {
where += tv + ""
} else {
where += tv + " " + tag + " "
}
}
}
return where, res
}
//更新数据
func (this *HoTimeDB) Update(table string, data Map, where Map) int64 {
query := "UPDATE " + table + " SET "
//UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson'
qs := make([]interface{}, 0)
tp := len(data)
for k, v := range data {
vstr := "?"
if Substr(k, len(k)-3, 3) == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
vstr = ObjToStr(v)
} else {
qs = append(qs, v)
}
query += "`" + k + "`=" + vstr + ""
if tp--; tp != 0 {
query += ", "
}
}
temp, resWhere := this.where(where)
//fmt.Println(resWhere)
query += temp
qs = append(qs, resWhere...)
res, err := this.Exec(query, qs...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
//如果更新成功,则删除缓存
if rows != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
return rows
}
func (this *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
query := "DELETE FROM " + table + " "
temp, resWhere := this.where(data)
query += temp
res, err := this.Exec(query, resWhere...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
//如果删除成功,删除对应缓存
if rows != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
//return 0
return rows
}
//插入新数据
func (this *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
values := make([]interface{}, 0)
queryString := " ("
valueString := " ("
lens := len(data)
tempLen := 0
for k, v := range data {
tempLen++
values = append(values, v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += "?,"
} else {
queryString += "`" + k + "`) "
valueString += "?);"
}
}
query := "INSERT INTO " + table + queryString + "VALUES" + valueString
res, err := this.Exec(query, values...)
id := int64(0)
if err.GetError() == nil && res != nil {
id, this.LastErr.err = res.LastInsertId()
}
//如果插入成功,删除缓存
if id != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
//fmt.Println(id)
return id
}
+1372
View File
File diff suppressed because it is too large Load Diff
+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 {
+11 -10
View File
@@ -2,37 +2,38 @@ package download
import (
"bytes"
"code.hoteas.com/golang/hotime"
. "code.hoteas.com/golang/hotime/common"
"io"
"io/ioutil"
"net/http"
"os"
)
//下载文件
func Down(url, path, name string) bool {
// Down 下载文件
func Down(url, path, name string, e ...*Error) bool {
os.MkdirAll(path, os.ModeDir)
if hotime.Substr(path, len(path)-1, len(path)) != "/" {
if Substr(path, len(path)-1, len(path)) != "/" {
path = path + "/"
}
out, err := os.Create(path + name)
e := hotime.Error{}
if err != nil {
e.SetError(err)
if err != nil && len(e) != 0 {
e[0].SetError(err)
return false
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
if err != nil && len(e) != 0 {
e[0].SetError(err)
return false
}
defer resp.Body.Close()
pix, err := ioutil.ReadAll(resp.Body)
_, err = io.Copy(out, bytes.NewReader(pix))
if err != nil {
e.SetError(err)
if err != nil && len(e) != 0 {
e[0].SetError(err)
return false
}
return true
+149
View File
@@ -0,0 +1,149 @@
package mongodb
import (
. "code.hoteas.com/golang/hotime/common"
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type MongoDb struct {
Client *mongo.Client
Ctx context.Context
DataBase *mongo.Database
Connect *mongo.Collection
LastErr error
}
func GetMongoDb(database, url string) (*MongoDb, error) {
db := MongoDb{}
clientOptions := options.Client().ApplyURI(url)
db.Ctx = context.TODO()
// Connect to MongoDb
var err error
db.Client, err = mongo.Connect(db.Ctx, clientOptions)
if err != nil {
return nil, err
}
// Check the connection
err = db.Client.Ping(db.Ctx, nil)
if err != nil {
return nil, err
}
fmt.Println("Connected to MongoDb!")
//databases, err := db.Client.ListDatabaseNames(db.Ctx, bson.M{})
//if err != nil {
// return nil, err
//}
//fmt.Println(databases)
db.DataBase = db.Client.Database(database)
return &db, nil
}
func (that *MongoDb) Insert(table string, data interface{}) string {
collection := that.DataBase.Collection(table)
re, err := collection.InsertOne(that.Ctx, data)
if err != nil {
that.LastErr = err
return ""
}
return ObjToStr(re.InsertedID)
}
func (that *MongoDb) InsertMany(table string, data ...interface{}) Slice {
collection := that.DataBase.Collection(table)
re, err := collection.InsertMany(that.Ctx, data)
if err != nil {
that.LastErr = err
return Slice{}
}
return ObjToSlice(re.InsertedIDs)
}
func (that *MongoDb) Update(table string, data Map, where Map) int64 {
collection := that.DataBase.Collection(table)
re, err := collection.UpdateMany(that.Ctx, where, data)
if err != nil {
that.LastErr = err
return 0
}
return re.ModifiedCount
}
func (that *MongoDb) Delete(table string, where Map) int64 {
collection := that.DataBase.Collection(table)
re, err := collection.DeleteMany(that.Ctx, where)
if err != nil {
that.LastErr = err
return 0
}
return re.DeletedCount
}
func (that *MongoDb) Get(table string, where Map) Map {
results := []Map{}
var cursor *mongo.Cursor
var err error
collection := that.DataBase.Collection(table)
if cursor, err = collection.Find(that.Ctx, where, options.Find().SetSkip(0), options.Find().SetLimit(2)); err != nil {
that.LastErr = err
return nil
}
//延迟关闭游标
defer func() {
if err := cursor.Close(that.Ctx); err != nil {
that.LastErr = err
}
}()
//这里的结果遍历可以使用另外一种更方便的方式:
if err = cursor.All(that.Ctx, &results); err != nil {
that.LastErr = err
return nil
}
if len(results) > 0 {
return results[0]
}
return nil
}
func (that MongoDb) Select(table string, where Map, page, pageRow int64) []Map {
page = (page - 1) * pageRow
if page < 0 {
page = 0
}
results := []Map{}
var cursor *mongo.Cursor
var err error
collection := that.DataBase.Collection(table)
if cursor, err = collection.Find(that.Ctx, where, options.Find().SetSkip(page), options.Find().SetLimit(pageRow)); err != nil {
that.LastErr = err
return results
}
//延迟关闭游标
defer func() {
if err := cursor.Close(that.Ctx); err != nil {
that.LastErr = err
}
}()
//这里的结果遍历可以使用另外一种更方便的方式:
if err = cursor.All(that.Ctx, &results); err != nil {
that.LastErr = err
return results
}
return results
}
+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()
}
+5 -5
View File
@@ -1,7 +1,7 @@
package upload
import (
"code.hoteas.com/golang/hotime"
. "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
}
@@ -50,8 +50,8 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
}
}
}
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + hotime.ObjToStr(hotime.Rand(6))
filePath = path + "/" + filename + this.Path
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
filePath = path + "/" + filename + that.Path
} else {
filePath = savePath
}
+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
}
-25
View File
@@ -1,25 +0,0 @@
package hotime
//框架层处理错误
type Error struct {
error
err error
}
func (this *Error) GetError() error {
return this.error
}
func (this *Error) SetError(err error, loglevel ...int) {
this.error = nil
if err == nil {
this.error = err
this.err = err
return
}
this.error = err
return
}
+419
View File
@@ -0,0 +1,419 @@
{
"flow": {
"admin": {
"sql": {
"org_id": "org_id",
"parent_ids[~]": "%,id,%",
"role_id": "role_id",
"zone_id": "zone_id"
},
"stop": true,
"table": "admin"
},
"channel": {
"sql": {
"admin_id": "id",
"org_id": "org_id",
"zone_id": "zone_id"
},
"stop": false,
"table": "channel"
},
"company": {
"sql": {
"admin_id": "id",
"org_id": "org_id",
"zone_id": "zone_id"
},
"stop": false,
"table": "company"
},
"logs": {
"sql": {
"admin_id": "id"
},
"stop": false,
"table": "logs"
},
"org": {
"sql": {
"admin_id": "id",
"parent_ids[~]": "%,org_id,%",
"zone_id": "zone_id"
},
"stop": true,
"table": "org"
},
"proj": {
"sql": {
"admin_id": "id",
"org_id": "org_id",
"sc_admin_id": "sc_admin_id",
"xm_admin_id": "xm_admin_id",
"zone_id": "zone_id"
},
"stop": false,
"table": "proj"
},
"role": {
"sql": {
"id": "role_id"
},
"stop": true,
"table": "role"
},
"sea": {
"sql": {
"admin_id": "id"
},
"stop": false,
"table": "sea"
},
"task": {
"sql": {
"admin_id": "id",
"zone_id": "zone_id"
},
"stop": false,
"table": "task"
},
"task_admin": {
"sql": {
"admin_id": "id",
"zone_id": "zone_id"
},
"stop": false,
"table": "task_admin"
},
"user": {
"sql": {
"admin_id": "id",
"zone_id": "zone_id"
},
"stop": false,
"table": "user"
},
"wechat": {
"sql": {
"admin_id": "id",
"org_id": "org_id"
},
"stop": false,
"table": "wechat"
},
"zone": {
"sql": {
"parent_ids[~]": "%,zone_id,%"
},
"stop": true,
"table": "zone"
}
},
"id": "023d5002b1e2c64ed744a12ac442c00e",
"label": "HoTime管理平台",
"labelConfig": {
"add": "添加",
"delete": "删除",
"download": "下载清单",
"edit": "编辑",
"info": "查看详情",
"show": "开启"
},
"menus": [
{
"auth": [
"show"
],
"icon": "Setting",
"label": "系统管理",
"menus": [
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "角色管理",
"table": "role"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "联络人管理",
"table": "user"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "项目分类",
"table": "category"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "标签管理",
"table": "out_tag"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "考勤管理",
"table": "time_tag"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "车辆管理",
"table": "car"
},
{
"auth": [
"show",
"download"
],
"label": "日志管理",
"table": "logs"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "系统设置",
"table": "system"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "标签管理",
"table": "visit_tag"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "城市管理",
"table": "city"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "项目管理",
"table": "proj"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "客户管理",
"table": "company"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "项目归口",
"table": "department"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "部门管理",
"table": "org"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "用章管理",
"table": "seal_tag"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "微信信息",
"table": "wechat"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "联盟公司",
"table": "zone"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "人员管理",
"table": "admin"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "供应商管理",
"table": "channel"
}
],
"name": "sys"
},
{
"auth": [
"show"
],
"icon": "Setting",
"label": "任务执行",
"menus": [
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "任务执行",
"table": "task_admin"
},
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "任务管理",
"table": "task"
}
],
"name": "sys:task"
},
{
"auth": [
"show"
],
"icon": "Setting",
"label": "跟进管理",
"menus": [
{
"auth": [
"show",
"add",
"delete",
"edit",
"info",
"download"
],
"label": "跟进管理",
"table": "sea"
}
],
"name": "sys:sea"
}
],
"name": "admin",
"stop": [
"role",
"org"
]
}
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
{
"cache": {
"db": {
"db": false,
"session": true,
"timeout": 7200
},
"memory": {
"db": true,
"session": true,
"timeout": 7200
}
},
"codeConfig": [
{
"config": "config/admin.json",
"configDB": "config/adminDB.json",
"mode": 0,
"name": "",
"rule": "config/rule.json",
"table": "admin"
}
],
"db": {
"mysql": {
"host": "192.168.6.253",
"name": "rfcb",
"password": "dasda8454456",
"port": "3306",
"user": "root"
}
},
"defFile": [
"index.html",
"index.htm"
],
"error": {
"1": "内部系统异常",
"2": "访问权限异常",
"3": "请求参数异常",
"4": "数据处理异常",
"5": "数据结果异常"
},
"mode": 2,
"port": "8081",
"sessionName": "HOTIME",
"tpt": "tpt"
}
+79
View File
@@ -0,0 +1,79 @@
{
"cache": {
"db": {
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除"
},
"memory": {
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除"
},
"redis": {
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
"host": "默认服务ip127.0.0.1,必须,如果需要使用redis服务时配置,",
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除"
},
"注释": "可配置memorydbredis,默认启用memory,默认优先级为memory\u003eredis\u003edb,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用"
},
"codeConfig": [
"注释:配置即启用,非必须,默认无",
{
"config": "默认config/app.json,必须,接口描述配置文件",
"configDB": "默认无,非必须,有则每次将数据库数据生成到此目录用于配置读写,无则不生成",
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据"
}
],
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
"db": {
"mysql": {
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认test,必须,数据库名称",
"password": "默认root,必须,数据库密码",
"port": "默认3306,必须,数据库端口",
"prefix": "默认空,非必须,数据表前缀",
"slave": {
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认test,必须,数据库名称",
"password": "默认root,必须,数据库密码",
"port": "默认3306,必须,数据库端口",
"user": "默认root,必须,数据库用户名",
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力"
},
"user": "默认root,必须,数据库用户名",
"注释": "除prefix及主从数据库slave项,其他全部必须"
},
"sqlite": {
"path": "默认config/data.db,必须,数据库位置"
},
"注释": "配置即启用,非必须,默认使用sqlite数据库"
},
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
"error": {
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
"2": "访问权限异常,没有登录或者登录异常等时候使用",
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
"注释": "web服务内置错误提示,自定义异常建议10开始"
},
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
"sessionName": "默认HOTIME,必须,设置session的cookie名",
"tlsCert": "默认空,非必须,https证书",
"tlsKey": "默认空,非必须,https密钥",
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能"
}
Binary file not shown.
+422
View File
@@ -0,0 +1,422 @@
[
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "idcard",
"strict": false,
"type": ""
},
{
"add": false,
"edit": false,
"info": true,
"list": true,
"must": false,
"name": "id",
"strict": true,
"type": ""
},
{
"add": false,
"edit": false,
"info": true,
"list": true,
"must": false,
"name": "sn",
"strict": false,
"type": ""
},
{
"add": false,
"edit": false,
"info": false,
"list": false,
"must": false,
"name": "parent_ids",
"strict": true,
"type": "index"
},
{
"add": false,
"edit": false,
"info": false,
"list": false,
"must": false,
"name": "index",
"strict": true,
"type": "index"
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "parent_id",
"true": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "amount",
"strict": true,
"type": "money"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "info",
"strict": false,
"type": "textArea"
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "status",
"strict": false,
"type": "select"
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "state",
"strict": false,
"type": "select"
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "sex",
"strict": false,
"type": "select"
},
{
"add": false,
"edit": false,
"info": false,
"list": false,
"must": false,
"name": "delete",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "lat",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "lng",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "latitude",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "longitude",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": false,
"list": false,
"must": false,
"name": "password",
"strict": false,
"type": "password"
},
{
"add": true,
"edit": true,
"info": false,
"list": false,
"must": false,
"name": "pwd",
"strict": false,
"type": "password"
},
{
"add": false,
"edit": false,
"info": false,
"list": false,
"must": false,
"name": "version",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "seq",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "sort",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "note",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "description",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "abstract",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "content",
"strict": false,
"type": "textArea"
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "address",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "full_name",
"strict": false,
"type": ""
},
{
"add": false,
"edit": false,
"info": true,
"list": false,
"must": false,
"name": "create_time",
"strict": true,
"type": "time"
},
{
"add": false,
"edit": false,
"info": true,
"list": true,
"must": false,
"name": "modify_time",
"strict": true,
"type": "time"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "image",
"strict": false,
"type": "image"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "img",
"strict": false,
"type": "image"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "avatar",
"strict": false,
"type": "image"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "icon",
"strict": false,
"type": "image"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "file",
"strict": false,
"type": "file"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "age",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "email",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "time",
"strict": false,
"type": "time"
},
{
"add": false,
"edit": false,
"info": true,
"list": false,
"must": false,
"name": "level",
"strict": false,
"type": ""
},
{
"add": true,
"edit": true,
"info": true,
"list": true,
"must": false,
"name": "rule",
"strict": false,
"type": "form"
},
{
"add": true,
"edit": true,
"info": true,
"list": false,
"must": false,
"name": "auth",
"strict": false,
"type": "auth"
},
{
"add": false,
"edit": false,
"info": true,
"list": true,
"must": false,
"name": "table",
"strict": false,
"type": "table"
},
{
"add": false,
"edit": false,
"info": true,
"list": true,
"must": false,
"name": "table_id",
"strict": false,
"type": "table_id"
}
]
+5 -94
View File
@@ -1,106 +1,17 @@
package main
import (
"code.hoteas.com/golang/hotime"
"code.hoteas.com/golang/hotime/tools/db"
"fmt"
//"go.hoteas.com/hotime/cache"
"golang.org/x/net/websocket"
"time"
//"go.hoteas.com/hotime/cache"
. "code.hoteas.com/golang/hotime"
)
func main() {
appIns := hotime.Application{}
appIns := Init("config/config.json")
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
appIns.SetConnectListener(func(context *hotime.Context) bool {
//fmt.Println(context.HandlerStr + time.Now().Format(" 2006-01-02 15:04 ") + hotime.Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")))
//this.HandlerStr = "/test.html"
return true
return isFinished
})
//手动模式,
appIns.SetConfig("example/config/config.json")
//redis缓存接入
//ca:=hotime.CacheIns(&cache.CacheRedis{Host:appIns.Config.GetString("redisHost"),Pwd:appIns.Config.GetString("redisPwd"),Time:appIns.Config.GetCeilInt64("cacheLongTime")})
//ca.Cache("xyzm","dasdas")
//ca.Cache("xyzn","dasdas")
//ca.Cache("xyzo","dasdas")
//ca.Cache("xyz*",nil)
//fmt.Println(ca.Cache("xyzm").Data)
//mysql
//mysql.SetDB(&appIns)
//自动选择数据库
db.SetDB(&appIns)
appIns.Run(Router{})
//appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
// query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
// "@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
// DB, e := sql.Open("mysql", query)
// if e != nil && len(err) != 0 {
// err[0].SetError(e)
// }
// return DB
//})
//内存缓存数据库数据,错误则删除
// appIns.Db.CacheIns=hotime.CacheIns(&hotime.CacheMemory{})
appIns.SetSession(hotime.CacheIns(&hotime.CacheMemory{}), hotime.CacheIns(&hotime.CacheDb{Db: &appIns.Db, Time: appIns.Config.GetInt64("cacheTime")}))
appIns.SetCache(hotime.CacheIns(&hotime.CacheMemory{}))
//快捷模式
//appIns.SetDefault(func(err ...*hotime.Error) *sql.DB {
// query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
// "@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
// DB, e := sql.Open("mysql", query)
// if e != nil && len(err) != 0 {
// err[0].SetError(e)
// }
// return DB
//})
appIns.Run(hotime.Router{
"app": hotime.Proj{
"index": hotime.Ctr{
"test": func(this *hotime.Context) {
fmt.Println(this.Db.GetTag())
//x:=this.Db.Action(func(db hotime.HoTimeDB) bool {
//
// db.Insert("user",hotime.Map{"unickname":"dasdas"})
//
// return true
//})
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)
},
},
},
})
}
-28
View File
@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Sample of websocket with golang</title>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function() {
var ws = new WebSocket("ws://localhost:8080/app/index/websocket");
ws.onmessage = function(e) {
$('<li>').text(e.data).appendTo($ul);
};
var $ul = $('#msg-list');
$('#sendBtn').click(function(){
var data = $('#name').val();
ws.send(data);
});
});
</script>
</head>
<body>
<input id="name" type="text"/>
<input type="button" id="sendBtn" value="send"/>
<ul id="msg-list"></ul>
</body>
</html>
+13 -7
View File
@@ -1,11 +1,17 @@
module hotime
module code.hoteas.com/golang/hotime
go 1.14
go 1.16
require (
code.hoteas.com/golang/hotime v0.8.3
github.com/garyburd/redigo v1.6.0
github.com/go-sql-driver/mysql v1.5.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
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
go.mongodb.org/mongo-driver v1.10.1
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
)
+104 -16
View File
@@ -1,19 +1,107 @@
code.hoteas.com/golang/hotime v0.8.2 h1:FncA1c6/GbFDjkTOXCaA7Eea24jAaqun+2ttxhR+6o0=
code.hoteas.com/golang/hotime v0.8.2/go.mod h1:NbboV59PdOAskM09JBLnK8Vn6lHCEiC5hJT4xhbsWOw=
code.hoteas.com/golang/hotime v0.8.3 h1:R2rdPtJJUk//1bmVUbLYnIn5adWtQOosaf8H1WyqXH8=
code.hoteas.com/golang/hotime v0.8.3/go.mod h1:NbboV59PdOAskM09JBLnK8Vn6lHCEiC5hJT4xhbsWOw=
code.hoteas.com/hoteas/hotime v0.8.2 h1:7rzjsQfEcGn16bZbdDqwkpuLQGVB5BT+AaaII6yLgyM=
code.hoteas.com/hoteas/hotime v0.8.2/go.mod h1:SGLzLvvi8eJmsgbGXABIniWvOq/dmu4i+K8e0CF8Qog=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.1 h1:NujsPveKwHaWuKUer/ceo9DzEe7HIj1SlJ6uvXZG0S4=
go.mongodb.org/mongo-driver v1.10.1/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
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=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+136
View File
@@ -0,0 +1,136 @@
package log
import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
func GetLog(path string, showCodeLine bool) *log.Logger {
hook := MyHook{
Field: "line",
Skip: 5,
Path: path, ShowCodeLine: showCodeLine,
}
loger := log.New()
loger.SetFormatter(&log.TextFormatter{})
loger.AddHook(&hook)
return loger
}
// MyHook ...
type MyHook struct {
Path string //存储日志的位置
ShowCodeLine bool //输出代码文件名称和日志行
Field string
Skip int
levels []log.Level
}
// Levels 只定义 error 和 panic 等级的日志,其他日志等级不会触发 hook
func (that *MyHook) Levels() []log.Level {
return log.AllLevels
}
// Fire 将异常日志写入到指定日志文件中
func (that *MyHook) Fire(entry *log.Entry) error {
if that.ShowCodeLine {
entry.Data[that.Field] = findCaller(that.Skip)
}
//不需要存储到文件
if that.Path == "" {
return nil
}
//存储到文件
logFilePath := time.Now().Format(that.Path)
err := os.MkdirAll(filepath.Dir(logFilePath), os.ModeAppend)
if err != nil {
return err
}
//os.Create(logFilePath)
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return err
}
bte, _ := entry.Bytes()
_, err = f.Write(bte)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
return nil
}
// 对caller进行递归查询, 直到找到非logrus包产生的第一个调用.
// 因为filename我获取到了上层目录名, 因此所有logrus包的调用的文件名都是 logrus/...
// 因此通过排除logrus开头的文件名, 就可以排除所有logrus包的自己的函数调用
func findCaller(skip int) string {
file := ""
line := 0
for i := 0; i < 10; i++ {
file, line = getCaller(skip + i)
if !strings.HasPrefix(file, "logrus") {
j := 0
for true {
j++
if file == "common/error.go" {
file, line = getCaller(skip + i + j)
}
if file == "db/hotimedb.go" {
file, line = getCaller(skip + i + j)
}
if file == "code/makecode.go" {
file, line = getCaller(skip + i + j)
}
if strings.Index(file, "common/") == 0 {
file, line = getCaller(skip + i + j)
}
if strings.Contains(file, "application.go") {
file, line = getCaller(skip + i + j)
}
if j == 5 {
break
}
}
break
}
}
return fmt.Sprintf("%s:%d", file, line)
}
// 这里其实可以获取函数名称的: fnName := runtime.FuncForPC(pc).Name()
// 但是我觉得有 文件名和行号就够定位问题, 因此忽略了caller返回的第一个值:pc
// 在标准库log里面我们可以选择记录文件的全路径或者文件名, 但是在使用过程成并发最合适的,
// 因为文件的全路径往往很长, 而文件名在多个包中往往有重复, 因此这里选择多取一层, 取到文件所在的上层目录那层.
func getCaller(skip int) (string, int) {
_, file, line, ok := runtime.Caller(skip)
//fmt.Println(file)
//fmt.Println(line)
if !ok {
return "", 0
}
n := 0
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
n++
if n >= 2 {
file = file[i+1:]
break
}
}
}
return file, line
}
+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)
},
},
},
-155
View File
@@ -1,155 +0,0 @@
package hotime
import (
"encoding/json"
"errors"
"reflect"
)
//hotime的常用map
type Map map[string]interface{}
//获取string
func (this Map) GetString(key string, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((this)[key])
}
func (this *Map) Pointer() *Map {
return this
}
//增加接口
func (this Map) Put(key string, value interface{}) {
//if this==nil{
// this=Map{}
//}
this[key] = value
}
//删除接口
func (this Map) Delete(key string) {
delete(this, key)
}
//获取Int
func (this Map) GetInt(key string, err ...*Error) int {
v := ObjToInt((this)[key], err...)
return v
}
//获取Int
func (this Map) GetInt64(key string, err ...*Error) int64 {
v := ObjToInt64((this)[key], err...)
return v
}
//获取向上取整Int64
func (this Map) GetCeilInt64(key string, err ...*Error) int64 {
v := ObjToCeilInt64((this)[key], err...)
return v
}
//获取向上取整Int
func (this Map) GetCeilInt(key string, err ...*Error) int {
v := ObjToCeilInt((this)[key], err...)
return v
}
//获取向上取整float64
func (this Map) GetCeilFloat64(key string, err ...*Error) float64 {
v := ObjToCeilFloat64((this)[key], err...)
return v
}
//获取Float64
func (this Map) GetFloat64(key string, err ...*Error) float64 {
v := ObjToFloat64((this)[key], err...)
return v
}
func (this Map) GetSlice(key string, err ...*Error) Slice {
//var v Slice
v := ObjToSlice((this)[key], err...)
return v
}
func (this Map) GetMap(key string, err ...*Error) Map {
//var data Slice
v := ObjToMap((this)[key], err...)
return v
}
func (this Map) Get(key string, err ...*Error) interface{} {
if v, ok := (this)[key]; ok {
return v
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
//请传递指针过来
func (this Map) ToStruct(stct interface{}) {
data := reflect.ValueOf(stct).Elem()
for k, v := range this {
ks := StrFirstToUpper(k)
dkey := data.FieldByName(ks)
if !dkey.IsValid() {
continue
}
switch dkey.Type().String() {
case "int":
dkey.SetInt(this.GetInt64(k))
case "int64":
dkey.Set(reflect.ValueOf(this.GetInt64(k)))
case "float64":
dkey.Set(reflect.ValueOf(this.GetFloat64(k)))
case "string":
dkey.Set(reflect.ValueOf(this.GetString(k)))
case "interface{}":
dkey.Set(reflect.ValueOf(v))
}
}
}
func (this Map) ToJsonString() string {
return ObjToStr(this)
}
func (this Map) JsonToMap(jsonStr string, err ...*Error) {
e := json.Unmarshal([]byte(jsonStr), &this)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
}
-93
View File
@@ -1,93 +0,0 @@
package hotime
//对象封装方便取用
type Obj struct {
Data interface{}
contextBase
}
func (this *Obj) Put(data interface{}) {
this.Data = data
}
func (this *Obj) ToInt(err ...Error) int {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToInt(this.Data, &this.Error)
}
func (this *Obj) ToInt64(err ...Error) int64 {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToInt64(this.Data, &this.Error)
}
func (this *Obj) ToFloat64(err ...Error) float64 {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToFloat64(this.Data, &this.Error)
}
//获取向上取整float64
func (this *Obj) ToCeilFloat64(err ...*Error) float64 {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilFloat64(this.Data, err...)
return v
}
func (this *Obj) ToStr() string {
return ObjToStr(this.Data)
}
func (this *Obj) ToMap(err ...Error) Map {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToMap(this.Data, &this.Error)
}
func (this *Obj) ToSlice(err ...Error) Slice {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToSlice(this.Data, &this.Error)
}
func (this *Obj) ToMapArray(err ...Error) []Map {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToMapArray(this.Data, &this.Error)
}
func (this *Obj) ToObj() interface{} {
return this.Data
}
//获取向上取整Int64
func (this *Obj) ToCeilInt64(err ...*Error) int64 {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilInt64(this.Data, err...)
return v
}
//获取向上取整Int
func (this *Obj) ToCeilInt(err ...*Error) int {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilInt(this.Data, err...)
return v
}
+26 -41
View File
@@ -1,71 +1,56 @@
package hotime
import (
. "code.hoteas.com/golang/hotime/cache"
. "code.hoteas.com/golang/hotime/common"
)
//session对象
type SessionIns struct {
ShortCache CacheIns
LongCache CacheIns
SessionId string
*HoTimeCache
SessionId string
Map
contextBase
ContextBase
}
func (this *SessionIns) set() {
if this.ShortCache != nil {
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
if this.LongCache != nil {
this.LongCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
func (that *SessionIns) set() {
that.HoTimeCache.Session(HEAD_SESSION_ADD+that.SessionId, that.Map)
}
func (this *SessionIns) Session(key string, data ...interface{}) *Obj {
func (that *SessionIns) Session(key string, data ...interface{}) *Obj {
if this.Map == nil {
this.get()
if that.Map == nil {
that.get()
}
if len(data) != 0 {
if data[0] == nil {
delete(this.Map, key)
this.set()
delete(that.Map, key)
that.set()
} else {
this.Map[key] = data[0]
this.set()
that.Map[key] = data[0]
that.set()
}
return &Obj{Data: nil}
}
return &Obj{Data: this.Map.Get(key)}
return &Obj{Data: that.Map.Get(key)}
}
func (this *SessionIns) get() {
func (that *SessionIns) get() {
if this.ShortCache != nil {
this.Map = this.ShortCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
if this.Map != nil {
return
}
that.Map = that.HoTimeCache.Session(HEAD_SESSION_ADD + that.SessionId).ToMap()
if that.Map != nil {
return
}
if this.LongCache != nil {
this.Map = this.LongCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
if this.Map != nil {
if this.ShortCache != nil {
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
return
}
}
this.Map = Map{}
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
that.Map = Map{}
that.HoTimeCache.Session(HEAD_SESSION_ADD+that.SessionId, that.Map)
return
}
func (this *SessionIns) Init(short CacheIns, long CacheIns) {
this.ShortCache = short
this.LongCache = long
func (that *SessionIns) Init(cache *HoTimeCache) {
that.HoTimeCache = cache
}
-87
View File
@@ -1,87 +0,0 @@
package hotime
import (
"errors"
)
type Slice []interface{}
//获取string
func (this Slice) GetString(key int, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((this)[key])
}
//获取Int
func (this Slice) GetInt(key int, err ...*Error) int {
v := ObjToInt((this)[key], err...)
return v
}
//获取Int
func (this Slice) GetInt64(key int, err ...*Error) int64 {
v := ObjToInt64((this)[key], err...)
return v
}
//获取向上取整Int64
func (this Slice) GetCeilInt64(key int, err ...*Error) int64 {
v := ObjToCeilInt64((this)[key], err...)
return v
}
//获取向上取整Int
func (this Slice) GetCeilInt(key int, err ...*Error) int {
v := ObjToCeilInt((this)[key], err...)
return v
}
//获取向上取整float64
func (this Slice) GetCeilFloat64(key int, err ...*Error) float64 {
v := ObjToCeilFloat64((this)[key], err...)
return v
}
//获取Float64
func (this Slice) GetFloat64(key int, err ...*Error) float64 {
v := ObjToFloat64((this)[key], err...)
return v
}
func (this Slice) GetSlice(key int, err ...*Error) Slice {
v := ObjToSlice((this)[key], err...)
return v
}
func (this Slice) GetMap(key int, err ...*Error) Map {
//var v Map
v := ObjToMap((this)[key], err...)
return v
}
func (this Slice) Get(key int, err ...*Error) interface{} {
if key < len(this) {
return this[key]
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
func (this Slice) Put(key int, value interface{}) {
this[key] = value
}
func (this Slice) ToJsonString() string {
return ObjToStr(this)
}
-18
View File
@@ -1,18 +0,0 @@
package db
import (
"code.hoteas.com/golang/hotime"
"code.hoteas.com/golang/hotime/tools/mysql"
"code.hoteas.com/golang/hotime/tools/sqlite"
"strings"
)
func SetDB(appIns *hotime.Application) {
if appIns.Config.GetString("dbType") == "sqlite" || strings.Index(appIns.Config.GetString("dbName"), ".db") == len(appIns.Config.GetString("dbName"))-3 {
appIns.Config["dbType"] = "sqlite"
sqlite.SetDB(appIns)
} else {
appIns.Config["dbType"] = "mysql"
mysql.SetDB(appIns)
}
}
-19
View File
@@ -1,19 +0,0 @@
package mysql
import (
"code.hoteas.com/golang/hotime"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func SetDB(appIns *hotime.Application) {
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
"@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
DB, e := sql.Open("mysql", query)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
return DB
})
}
-17
View File
@@ -1,17 +0,0 @@
package sqlite
import (
"code.hoteas.com/golang/hotime"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func SetDB(appIns *hotime.Application) {
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
db, e := sql.Open("sqlite3", appIns.Config.GetString("dbName"))
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
return db
})
}
+2 -15
View File
@@ -1,21 +1,8 @@
package hotime
//控制器
// Ctr 控制器
type Ctr map[string]Method
type Proj map[string]Ctr
type Router map[string]Proj
type MethodRouter map[string]Method //直接字符串关联函数
type Method func(this *Context)
type CacheIns interface {
//set(key string, value interface{}, time int64)
//get(key string) interface{}
//delete(key string)
Cache(key string, data ...interface{}) *Obj
}
//单条缓存数据
type cacheData struct {
time int64
data interface{}
}
type Method func(that *Context)
+110 -43
View File
@@ -1,21 +1,40 @@
package hotime
import (
. "code.hoteas.com/golang/hotime/common"
)
var IsRun = false //当前状态
var App = map[string]*Application{} //整个项目
//var Db = HoTimeDB{} //数据库实例
var Config = Map{
"debug": 1, //debug 0关闭1开启
"logLevel": LOG_FMT,
"dbHost": "127.0.0.1",
"dbName": "test",
"dbUser": "root",
"dbPwd": "root",
"dbPort": "3306",
"dbCached": 0, //0不开启缓存
"cacheShortTime": 60 * 60 * 2,
"cacheLongTime": 60 * 60 * 24 * 30,
"mode": 2, //模式 0生产模式,1,测试模式,2,开发模式
//"codeConfig": Map{
// "admin": "config/app.json",
//},
"codeConfig": Slice{
Map{
"table": "admin", //默认admin,必须,有则根据数据库内当前表名做为用户生成数据
"name": "", //默认admin,非必须,有则生成代码到此目录,无则采用缺省模式使用表名
"config": "config/app.json", //默认config/app.json,必须,接口描述配置文件,无则自动生成
"rule": "config/rule.json", //默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成
"mode": 0, //默认0,非必须,0为内嵌代码模式,1为生成代码模式
},
},
"db": Map{
"sqlite": Map{
"path": "config/data.db",
},
},
"cache": Map{
"memory": Map{
"timeout": 60 * 60 * 2,
"db": true,
"session": true,
},
},
"error": Map{
"1": "内部系统异常",
"2": "访问权限异常",
@@ -23,32 +42,81 @@ var Config = Map{
"4": "数据处理异常",
"5": "数据结果异常",
},
"tpt": "tpt",
"defFile": []string{"index.html", "index.htm"},
"modeRouterStrict": false, //路由严格模式/a/b/c
"port": "80",
"sessionName": "HOTIME",
"tlsPort": "0",
"tlsKey": "",
"tlsCert": "",
"tpt": "tpt",
"defFile": []string{"index.html", "index.htm"},
"port": "80",
"sessionName": "HOTIME",
}
var ConfigNote = Map{
"logLevel": "日志等级0关闭,1打印",
"logFile": "如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"debug": "是否开启debug模式,0关闭,其他开启,debug模式下日志展示更全", //debug 0关闭1开启
"dbHost": "数据库ip地址默认127.0.0.1",
"dbName": "数据库名称,sqlite为文件路径比如a/x.db",
"dbUser": "数据库用户名",
"dbPwd": "数据库密码",
"dbPort": "数据库端口",
"dbType": "如果需要使用自动数据库配置,请设置此项,手动配置数据库不需要,目前支持mysql,sqlite",
"dbCached": "是否开启数据库缓存0为关闭,其他开启", //0不开启缓存
"redisHost": "如果需要使用redis服务时配置,默认服务ip127.0.0.1",
"redisPort": "如果需要使用redis服务时配置,默认服务端口:6379",
"redisPwd": "如果需要使用redis服务时配置,默认服务密码:123456",
"cacheShortTime": "两级缓存,短缓存存储时间60 * 60 * 2,一般为内存缓存",
"cacheLongTime": "两级缓存,长缓存存储时间60 * 60 * 24 * 30,一般为数据库或者redis缓存",
"logLevel": "默认0,必须0关闭,1打印,日志等级",
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能",
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存", //debug 0关闭1开启
//"codeConfig": Map{
// "注释": "配置即启用,非必须,默认无",
// //"package":"默认admin,必须,mode模式为2时会自动生成包文件夹和代码文件",
// //"path":""
//
// "packageName": "默认无,必须,包名称以及应用名,生成代码的配置文件地址,比如config/app.json,数据库有更新时自动更新配置文件以及对应的生成文件",
//},
"codeConfig": Slice{
"注释:配置即启用,非必须,默认无",
Map{ //默认无,必须,接口类别名称
//"注释": "", //
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据",
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
"config": "默认config/app.json,必须,接口描述配置文件", //
"configDB": "默认无,非必须,有则每次将数据库数据生成到此目录用于配置读写,无则不生成", //
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
},
},
"db": Map{
"注释": "配置即启用,非必须,默认使用sqlite数据库",
"mysql": Map{
"注释": "除prefix及主从数据库slave项,其他全部必须",
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认test,必须,数据库名称",
"user": "默认root,必须,数据库用户名",
"password": "默认root,必须,数据库密码",
"port": "默认3306,必须,数据库端口",
"prefix": "默认空,非必须,数据表前缀",
"slave": Map{
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力",
"host": "默认127.0.0.1,必须,数据库ip地址",
"name": "默认test,必须,数据库名称",
"user": "默认root,必须,数据库用户名",
"password": "默认root,必须,数据库密码",
"port": "默认3306,必须,数据库端口",
},
},
"sqlite": Map{
"path": "默认config/data.db,必须,数据库位置",
},
},
"cache": Map{
"注释": "可配置memorydbredis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用",
"memory": Map{
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除",
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
},
"db": Map{
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除",
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
},
"redis": Map{
"host": "默认服务ip127.0.0.1,必须,如果需要使用redis服务时配置,",
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除",
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
},
},
"error": Map{
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
"2": "访问权限异常,没有登录或者登录异常等时候使用",
@@ -57,14 +125,13 @@ var ConfigNote = Map{
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
"注释": "web服务内置错误提示,自定义异常建议10开始",
},
"tpt": "静态文件目录,默认为程序目录下tpt目录",
"defFile": "默认访问文件,默认访问index.html或者index.htm文件",
"crossDomain": "如果需要跨域设置,空字符串为不开启,*为开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域", //是否开启跨域
"modeRouterStrict": "路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配", //路由严格模式/a/b/c
"connectLogShow": "如果需要web访问链接、访问ip、访问时间打印,0为关闭其他数字开启此功能",
"sessionName": "设置session的cookie名默认HOTIME",
"port": "web服务开启Http端口,0为不启用http服务,默认80",
"tlsPort": "web服务https端口,0为不启用https服务",
"tlsKey": "https密钥",
"tlsCert": "https证书",
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域", //是否开启跨域
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配", //路由严格模式/a/b/c
"sessionName": "默认HOTIME,必须,设置session的cookie名",
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
"tlsKey": "默认空,非必须,https密钥",
"tlsCert": "默认空,非必须,https证书",
}
-1
View File
@@ -1 +0,0 @@
/.idea/*
-455
View File
@@ -1,455 +0,0 @@
package hotime
import (
"bytes"
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
)
type Application struct {
MethodRouter
Router
contextBase
Port string //端口号
TLSPort string //ssl访问端口号
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
connectDbFunc func(err ...*Error) *sql.DB
configPath string
Config Map
Db HoTimeDB
Server *http.Server
CacheIns
sessionLong CacheIns
sessionShort CacheIns
http.Handler
}
func (this *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
this.handler(w, req)
}
//启动实例
func (this *Application) Run(router Router) {
//如果没有设置配置自动生成配置
if this.configPath == "" || len(this.Config) == 0 {
this.SetConfig()
}
//防止手动设置缓存误伤
if this.CacheIns == nil {
this.SetCache(CacheIns(&CacheMemory{}))
}
//防止手动设置session误伤
if this.sessionShort == nil && this.sessionLong == nil {
if this.connectDbFunc == nil {
this.SetSession(CacheIns(&CacheMemory{}), nil)
} else {
this.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &this.Db, Time: this.Config.GetInt64("cacheLongTime")}))
}
}
this.Router = router
//重新设置MethodRouter//直达路由
this.MethodRouter = MethodRouter{}
modeRouterStrict := true
if this.Config.Get("modeRouterStrict").(bool) == false {
modeRouterStrict = false
}
if router != nil {
for pk, pv := range router {
if !modeRouterStrict {
pk = strings.ToLower(pk)
}
if pv != nil {
for ck, cv := range pv {
if !modeRouterStrict {
ck = strings.ToLower(ck)
}
if cv != nil {
for mk, mv := range cv {
if !modeRouterStrict {
mk = strings.ToLower(mk)
}
this.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
}
}
}
}
}
}
//this.Port = port
this.Port = this.Config.GetString("port")
this.TLSPort = this.Config.GetString("tlsPort")
if this.connectDbFunc != nil && (this.Db.DB == nil || this.Db.DB.Ping() != nil) {
this.Db.SetConnect(this.connectDbFunc)
}
if this.CacheIns == nil {
this.CacheIns = CacheIns(&CacheMemory{Map: Map{}, Time: this.Config.GetInt64("cacheShortTime")})
}
//异常处理
defer func() {
if err := recover(); err != nil {
//this.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
logFmt(err, 2, LOG_ERROR)
this.Run(router)
}
}()
this.Server = &http.Server{}
if !IsRun {
IsRun = true
}
ch := make(chan int)
if ObjToCeilInt(this.Port) != 0 {
go func() {
App[this.Port] = this
this.Server.Handler = this
//启动服务
this.Server.Addr = ":" + this.Port
err := this.Server.ListenAndServe()
logFmt(err, 2)
ch <- 1
}()
} else if ObjToCeilInt(this.TLSPort) != 0 {
go func() {
App[this.TLSPort] = this
this.Server.Handler = this
//启动服务
this.Server.Addr = ":" + this.TLSPort
err := this.Server.ListenAndServeTLS(this.Config.GetString("tlsCert"), this.Config.GetString("tlsKey"))
logFmt(err, 2)
ch <- 2
}()
} else {
logFmt("没有端口启用", 2, LOG_INFO)
return
}
value := <-ch
logFmt("启动服务失败 : "+ObjToStr(value), 2, LOG_ERROR)
}
//启动实例
func (this *Application) SetConnectDB(connect func(err ...*Error) *sql.DB) {
//this.Db.DBCached=false
//if this.Config.GetCeilInt("dbCached")!=0{
// this.Db.DBCached=true
//}
this.connectDbFunc = connect
this.Db.SetConnect(this.connectDbFunc)
this.Db.DBCached = false
if this.Config.GetCeilInt("dbCached") != 0 {
this.Db.DBCached = true
}
this.Db.Type = this.Config.GetString("dbType")
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetSession(short CacheIns, Long CacheIns) {
this.sessionLong = Long
this.sessionShort = short
}
//默认配置缓存和session实现
func (this *Application) SetDefault(connect func(err ...*Error) *sql.DB) {
this.SetConfig()
if connect != nil {
this.connectDbFunc = connect
this.Db.SetConnect(this.connectDbFunc)
}
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetCache(cache CacheIns) {
this.CacheIns = cache
}
//设置配置文件路径全路径或者相对路径
func (this *Application) SetConfig(configPath ...string) {
if len(configPath) != 0 {
this.configPath = configPath[0]
}
if this.configPath == "" {
this.configPath = "config/config.json"
}
//加载配置文件
btes, err := ioutil.ReadFile(this.configPath)
this.Config = DeepCopyMap(Config).(Map)
if err == nil {
cmap := Map{}
//文件是否损坏
cmap.JsonToMap(string(btes), &this.Error)
for k, v := range cmap {
this.Config[k] = v //程序配置
Config[k] = v //系统配置
}
} else {
logFmt("配置文件不存在,或者配置出错,使用缺省默认配置", 2)
}
//文件如果损坏则不写入配置防止配置文件数据丢失
if this.Error.GetError() == nil {
var configByte bytes.Buffer
err = json.Indent(&configByte, []byte(this.Config.ToJsonString()), "", "\t")
//判断配置文件是否序列有变化有则修改配置,五则不变
//fmt.Println(len(btes))
if len(btes) != 0 && configByte.String() == string(btes) {
return
}
//写入配置说明
var configNoteByte bytes.Buffer
json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
os.MkdirAll(filepath.Dir(this.configPath), os.ModeDir)
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
if err != nil {
this.Error.SetError(err)
}
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
}
}
//连接判断,返回true继续传输至控制层,false则停止传输
func (this *Application) SetConnectListener(lis func(this *Context) bool) {
this.connectListener = append(this.connectListener, lis)
}
//网络错误
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
//
//}
//序列化链接
func (this *Application) urlSer(url string) (string, []string) {
q := strings.Index(url, "?")
if q == -1 {
q = len(url)
}
o := Substr(url, 0, q)
r := strings.SplitN(o, "/", -1)
var s = make([]string, 0)
for i := 0; i < len(r); i++ {
if !strings.EqualFold("", r[i]) {
s = append(s, r[i])
}
}
return o, s
}
//访问
func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
_, s := this.urlSer(req.RequestURI)
//获取cookie
// 如果cookie存在直接将sessionId赋值为cookie.Value
// 如果cookie不存在就查找传入的参数中是否有token
// 如果token不存在就生成随机的sessionId
// 如果token存在就判断token是否在Session中有保存
// 如果有取出token并复制给cookie
// 没有保存就生成随机的session
cookie, err := req.Cookie(this.Config.GetString("sessionName"))
sessionId := Md5(strconv.Itoa(Rand(10)))
token := req.FormValue("token")
//isFirst:=false
if err != nil || (len(token) == 32 && cookie.Value != token) {
if len(token) == 32 {
sessionId = token
}
//else{
// isFirst=true;
//}
http.SetCookie(w, &http.Cookie{Name: this.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
} else {
sessionId = cookie.Value
}
unescapeUrl, err := url.QueryUnescape(req.RequestURI)
if err != nil {
unescapeUrl = req.RequestURI
}
//访问实例
context := Context{SessionIns: SessionIns{SessionId: sessionId,
LongCache: this.sessionLong,
ShortCache: this.sessionShort,
},
CacheIns: this.CacheIns,
Resp: w, Req: req, Application: this, RouterString: s, Config: this.Config, Db: &this.Db, HandlerStr: unescapeUrl}
//header默认设置
header := w.Header()
header.Set("Content-Type", "text/html; charset=utf-8")
//url去掉参数并序列化
context.HandlerStr, context.RouterString = this.urlSer(context.HandlerStr)
//跨域设置
this.crossDomain(&context)
//是否展示日志
if this.Config.GetInt("connectLogShow") != 0 {
logFmt(Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))+" "+context.HandlerStr, 0, LOG_INFO)
}
//访问拦截true继续false暂停
connectListenerLen := len(this.connectListener)
if connectListenerLen != 0 {
for i := 0; i < connectListenerLen; i++ {
if !this.connectListener[i](&context) {
context.View()
return
}
}
}
//接口服务
//if len(s) == 3 {
// //如果满足规则则路由到对应控制器去
// if this.Router[s[0]] != nil && this.Router[s[0]][s[1]] != nil && this.Router[s[0]][s[1]][s[2]] != nil {
// //控制层
// this.Router[s[0]][s[1]][s[2]](&context)
// //header.Set("Content-Type", "text/html; charset=utf-8")
// context.View()
// return
// }
//
//}
//验证接口严格模式
modeRouterStrict := this.Config.Get("modeRouterStrict").(bool)
tempHandlerStr := context.HandlerStr
if !modeRouterStrict {
tempHandlerStr = strings.ToLower(tempHandlerStr)
}
//执行接口
if this.MethodRouter[tempHandlerStr] != nil {
this.MethodRouter[tempHandlerStr](&context)
context.View()
return
}
//url赋值
path := this.Config.GetString("tpt") + tempHandlerStr
//判断是否为默认
if path[len(path)-1] == '/' {
defFile := this.Config.GetSlice("defFile")
for i := 0; i < len(defFile); i++ {
temp := path + defFile.GetString(i)
_, err := os.Stat(temp)
if err == nil {
path = temp
break
}
}
if path[len(path)-1] == '/' {
w.WriteHeader(404)
return
}
}
if strings.Contains(path, "/.") {
w.WriteHeader(404)
return
}
//设置header
delete(header, "Content-Type")
if this.Config.GetInt("debug") != 1 {
header.Set("Cache-Control", "public")
}
if strings.Index(path, ".m3u8") != -1 {
header.Add("Content-Type", "audio/mpegurl")
}
//w.Write(data)
http.ServeFile(w, req, path)
}
func (this *Application) crossDomain(context *Context) {
//没有跨域设置
if context.Config.GetString("crossDomain") == "" {
return
}
header := context.Resp.Header()
header.Set("Access-Control-Allow-Origin", "*")
header.Set("Access-Control-Allow-Methods", "*")
header.Set("Access-Control-Allow-Credentials", "true")
header.Set("Access-Control-Expose-Headers", "*")
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
if context.Config.GetString("crossDomain") != "*" {
header.Set("Access-Control-Allow-Origin", this.Config.GetString("crossDomain"))
return
}
origin := context.Req.Header.Get("Origin")
if origin != "" {
header.Set("Access-Control-Allow-Origin", origin)
return
}
refer := context.Req.Header.Get("Referer")
if refer != "" {
tempInt := 0
lastInt := strings.IndexFunc(refer, func(r rune) bool {
if r == '/' && tempInt > 8 {
return true
}
tempInt++
return false
})
if lastInt < 0 {
lastInt = len(refer)
}
refer = Substr(refer, 0, lastInt)
header.Set("Access-Control-Allow-Origin", refer)
}
}
-146
View File
@@ -1,146 +0,0 @@
package hotime
import (
"encoding/json"
"strings"
"time"
)
type CacheDb struct {
Time int64
Db *HoTimeDB
contextBase
isInit bool
}
func (this *CacheDb) initDbTable() {
if this.isInit {
return
}
if this.Db.Type == "mysql" {
dbNames := this.Db.Query("SELECT DATABASE()")
if len(dbNames) == 0 {
return
}
dbName := dbNames[0].GetString("DATABASE()")
res := this.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='cached'")
if len(res) != 0 {
this.isInit = true
return
}
_, e := this.Db.Exec("CREATE TABLE `cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
if e.GetError() == nil {
this.isInit = true
}
}
if this.Db.Type == "sqlite" {
res := this.Db.Query(`select * from sqlite_master where type = 'table' and name = 'cached'`)
if len(res) != 0 {
this.isInit = true
return
}
_, e := this.Db.Exec(`CREATE TABLE "cached" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"ckey" TEXT(60),
"cvalue" TEXT(2000),
"time" integer,
"endtime" integer
);`)
if e.GetError() == nil {
this.isInit = true
}
}
}
//获取Cache键只能为string类型
func (this *CacheDb) get(key string) interface{} {
cached := this.Db.Get("cached", "*", Map{"ckey": key})
if cached == nil {
return nil
}
//data:=cacheMap[key];
if cached.GetInt64("endtime") <= time.Now().Unix() {
this.Db.Delete("cached", Map{"id": cached.GetString("id")})
return nil
}
data := Map{}
data.JsonToMap(cached.GetString("cvalue"))
return data.Get("data")
}
//key value ,时间为时间戳
func (this *CacheDb) set(key string, value interface{}, tim int64) {
bte, _ := json.Marshal(Map{"data": value})
num := this.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
if num == int64(0) {
this.Db.Insert("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
}
//随机执行删除命令
if Rand(1000) > 950 {
this.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
}
}
func (this *CacheDb) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
this.Db.Delete("cached", Map{"ckey": key + "%"})
} else {
this.Db.Delete("cached", Map{"ckey": key})
}
}
func (this *CacheDb) Cache(key string, data ...interface{}) *Obj {
this.initDbTable()
if len(data) == 0 {
return &Obj{Data: this.get(key)}
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
this.delete(key)
return &Obj{Data: nil}
}
if len(data) == 1 {
if this.Time == 0 {
this.Time = Config.GetInt64("cacheLongTime")
}
tim += this.Time
}
if len(data) == 2 {
this.SetError(nil)
tempt := ObjToInt64(data[1], &this.Error)
if tempt > tim {
tim = tempt
} else if this.GetError() == nil {
tim = tim + tempt
}
}
this.set(key, data[0], tim)
return &Obj{Data: nil}
}
-146
View File
@@ -1,146 +0,0 @@
package hotime
import (
"strings"
"sync"
"time"
)
type CacheMemory struct {
Time int64
Map
contextBase
mutex *sync.RWMutex
}
//获取Cache键只能为string类型
func (this *CacheMemory) get(key string) interface{} {
this.Error.SetError(nil)
if this.Map == nil {
this.Map = Map{}
}
if this.Map[key] == nil {
return nil
}
data := this.Map.Get(key, &this.Error).(cacheData)
if this.GetError() != nil {
return nil
}
if data.time < time.Now().Unix() {
delete(this.Map, key)
return nil
}
return data.data
}
func (this *CacheMemory) refreshMap() {
go func() {
this.mutex.Lock()
defer this.mutex.Unlock()
for key, v := range this.Map {
data := v.(cacheData)
if data.time <= time.Now().Unix() {
delete(this.Map, key)
}
}
}()
}
//key value ,时间为时间戳
func (this *CacheMemory) set(key string, value interface{}, time int64) {
this.Error.SetError(nil)
var data cacheData
if this.Map == nil {
this.Map = Map{}
}
dd := this.Map[key]
if dd == nil {
data = cacheData{}
} else {
data = dd.(cacheData)
}
data.time = time
data.data = value
this.Map.Put(key, data)
}
func (this *CacheMemory) delete(key string) {
del := strings.Index(key, "*")
//如果通配删除
if del != -1 {
key = Substr(key, 0, del)
for k, _ := range this.Map {
if strings.Index(k, key) != -1 {
delete(this.Map, key)
}
}
} else {
delete(this.Map, key)
}
}
func (this *CacheMemory) Cache(key string, data ...interface{}) *Obj {
x := RandX(1, 100000)
if x > 99950 {
this.refreshMap()
}
if this.mutex == nil {
this.mutex = &sync.RWMutex{}
}
reData := &Obj{Data: nil}
if len(data) == 0 {
this.mutex.RLock()
reData.Data = this.get(key)
this.mutex.RUnlock()
return reData
}
tim := time.Now().Unix()
if len(data) == 1 && data[0] == nil {
this.mutex.Lock()
this.delete(key)
this.mutex.Unlock()
return reData
}
if len(data) == 1 {
if this.Time == 0 {
this.Time = Config.GetInt64("cacheShortTime")
}
tim = tim + this.Time
}
if len(data) == 2 {
this.Error.SetError(nil)
tempt := ObjToInt64(data[1], &this.Error)
if tempt > tim {
tim = tempt
} else if this.GetError() == nil {
tim = tim + tempt
}
}
this.mutex.Lock()
this.set(key, data[0], tim)
this.mutex.Unlock()
return reData
}
-16
View File
@@ -1,16 +0,0 @@
package hotime
type LOG_MODE int
const (
LOG_NIL = 0
LOG_FMT = 1
//LOG_FILE = 2
LOG_INFO LOG_MODE = 0
LOG_WARN LOG_MODE = 1
LOG_ERROR LOG_MODE = 2
)
//session储存头
const HEAD_SESSION_ADD = "session#"
-65
View File
@@ -1,65 +0,0 @@
package hotime
import (
"encoding/json"
"errors"
"net/http"
)
type Context struct {
contextBase
Resp http.ResponseWriter
Req *http.Request
Application *Application
RouterString []string
Config Map
Db *HoTimeDB
RespData Map
CacheIns
SessionIns
HandlerStr string //复写请求url
}
//唯一标志
func (this *Context) Mtd(router [3]string) Map {
this.Application.Router[router[0]][router[1]][router[2]](this)
d := this.RespData
this.RespData = nil
return d
}
//打印
func (this *Context) Display(statu int, data interface{}) {
resp := Map{"statu": statu}
if statu != 0 {
temp := Map{}
tpe := this.Config.GetMap("error").GetString(ObjToStr(statu))
if tpe == "" {
logFmt(errors.New("找不到对应的错误码"), 2, LOG_WARN)
}
temp["type"] = tpe
temp["msg"] = data
resp["result"] = temp
} else {
resp["result"] = data
}
this.RespData = resp
//this.Data=d;
}
func (this *Context) View() {
if this.RespData == nil {
return
}
d, err := json.Marshal(this.RespData)
if err != nil {
return
}
this.RespData = nil
this.Resp.Write(d)
}
-17
View File
@@ -1,17 +0,0 @@
package hotime
import "time"
type contextBase struct {
Error
tag int64
}
//唯一标志
func (this *contextBase) GetTag() int64 {
if this.tag == int64(0) {
this.tag = time.Now().UnixNano()
}
return this.tag
}
-935
View File
@@ -1,935 +0,0 @@
package hotime
import (
"database/sql"
"encoding/json"
"errors"
"os"
"reflect"
"strings"
)
type HoTimeDB struct {
*sql.DB
contextBase
CacheIns
Type string
DBCached bool
LastQuery string
LastData []interface{}
ConnectFunc func(err ...*Error) *sql.DB
LastErr Error
limit Slice
Tx *sql.Tx //事务对象
}
//设置数据库配置连接
func (this *HoTimeDB) SetConnect(connect func(err ...*Error) *sql.DB, err ...*Error) {
this.ConnectFunc = connect
this.InitDb()
}
//事务,如果action返回true则执行成功;false则回滚
func (this *HoTimeDB) Action(action func(db HoTimeDB) bool) bool {
db := HoTimeDB{DB: this.DB, CacheIns: this.CacheIns, DBCached: this.DBCached}
tx, err := db.Begin()
if err != nil {
this.LastErr.SetError(err)
return false
}
db.Tx = tx
result := action(db)
if !result {
db.Tx.Rollback()
return result
}
db.Tx.Commit()
return result
}
func (this *HoTimeDB) InitDb(err ...*Error) Error {
if len(err) != 0 {
this.LastErr = *(err[0])
}
this.DB = this.ConnectFunc(&this.LastErr)
if this.DB == nil {
return this.LastErr
}
e := this.DB.Ping()
this.LastErr.SetError(e)
return this.LastErr
}
func (this *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
page = (page - 1) * pageRow
if page < 0 {
page = 1
}
this.limit = Slice{page, pageRow}
return this
}
func (this *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
if len(qu) == 1 {
qu = append(qu, Map{"LIMIT": this.limit})
}
if len(qu) == 2 {
temp := qu[1].(Map)
temp["LIMIT"] = this.limit
qu[1] = temp
}
if len(qu) == 3 {
temp := qu[2].(Map)
temp["LIMIT"] = this.limit
qu[2] = temp
}
//fmt.Println(qu)
data := this.Select(table, qu...)
return data
}
//数据库数据解析
func (this *HoTimeDB) Row(resl *sql.Rows) []Map {
dest := make([]Map, 0)
strs, _ := resl.Columns()
for i := 0; resl.Next(); i++ {
lis := make(Map, 0)
a := make([]interface{}, len(strs))
b := make([]interface{}, len(a))
for j := 0; j < len(a); j++ {
b[j] = &a[j]
}
resl.Scan(b...)
for j := 0; j < len(a); j++ {
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
lis[strs[j]] = string(a[j].([]byte))
} else {
lis[strs[j]] = a[j]
}
}
//防止int被误读为float64
jlis, e := json.Marshal(lis)
if e != nil {
this.LastErr.SetError(e)
} else {
lis.JsonToMap(string(jlis), &this.LastErr)
}
dest = append(dest, lis)
}
return dest
}
//
////code=0,1,2 0 backup all,1 backup data,2 backup ddl
//func (this *HoTimeDB) Backup(path string, code int) {
// var cmd *exec.Cmd
// switch code {
// case 0:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
// case 1:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]), ObjToStr(Config["dbName"]))
// case 2:cmd= exec.Command("mysqldump","--no-data","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
// }
//
//
//
// stdout, err := cmd.StdoutPipe()
// if err != nil {
// log.Println(err)
// }
//
// if err := cmd.Start(); err != nil {
// log.Println(err)
// }
//
// bytes, err := ioutil.ReadAll(stdout)
// if err != nil {
// log.Println(err)
// }
// err = ioutil.WriteFile(path, bytes, 0644)
// if err != nil {
// panic(err)
// }
// return ;
// //
// //db := ``
// //fmt.Println(db)
// //
// //tables := this.Query("show tables")
// //lth := len(tables)
// //if lth == 0 {
// // return
// //}
// //for k, _ := range tables[0] {
// // db = Substr(k, 10, len(k))
// //}
// //
// //fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
// //fd.Write([]byte("/*datetime " + time.Now().Format("2006-01-02 15:04:05") + " */ \r\n"))
// //fd.Close()
// //
// //for i := 0; i < lth; i++ {
// // tt := tables[i]["Tables_in_"+db].(string)
// // this.backupSave(path, tt, code)
// // debug.FreeOSMemory()
// //}
//
//}
func (this *HoTimeDB) backupSave(path string, tt string, code int) {
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
defer fd.Close()
str := "\r\n"
if code == 0 || code == 2 {
str += this.backupDdl(tt)
}
if code == 0 || code == 1 {
str += "insert into `" + tt + "`\r\n\r\n("
str += this.backupCol(tt)
}
fd.Write([]byte(str))
}
func (this *HoTimeDB) backupDdl(tt string) string {
data := this.Query("show create table " + tt)
if len(data) == 0 {
return ""
}
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
}
func (this *HoTimeDB) backupCol(tt string) string {
str := ""
data := this.Select(tt, "*")
lthData := len(data)
if lthData == 0 {
return str
}
lthCol := len(data[0])
col := make([]string, lthCol)
tempLthData := 0
for k, _ := range data[0] {
if tempLthData == lthCol-1 {
str += "`" + k + "`)"
} else {
str += "`" + k + "`,"
}
col[tempLthData] = k
tempLthData++
}
str += " values"
for j := 0; j < lthData; j++ {
for m := 0; m < lthCol; m++ {
if m == 0 {
str += "("
}
v := "NULL"
if data[j][col[m]] != nil {
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
}
if m == lthCol-1 {
str += v + ")"
} else {
str += v + ","
}
}
if j == lthData-1 {
str += ";\r\n\r\n"
} else {
str += ",\r\n\r\n"
}
}
return str
}
func (this *HoTimeDB) md5(query string, args ...interface{}) string {
strByte, _ := json.Marshal(args)
str := Md5(query + ":" + string(strByte))
return str
}
func (this *HoTimeDB) Query(query string, args ...interface{}) []Map {
//fmt.Println(query)
var err error
var resl *sql.Rows
this.LastQuery = query
this.LastData = args
if this.DB == nil {
err = errors.New("没有初始化数据库")
this.LastErr.SetError(err)
return nil
}
if this.Tx != nil {
resl, err = this.Tx.Query(query, args...)
} else {
resl, err = this.DB.Query(query, args...)
}
this.LastErr.SetError(err)
if err != nil {
if err = this.DB.Ping(); err != nil {
this.LastErr.SetError(err)
this.InitDb()
if this.LastErr.GetError() != nil {
return nil
}
return this.Query(query, args...)
}
return nil
}
return this.Row(resl)
}
func (this *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, Error) {
this.LastQuery = query
this.LastData = args
var e error
var resl sql.Result
if this.DB == nil {
err := errors.New("没有初始化数据库")
this.LastErr.SetError(err)
return nil, this.LastErr
}
if this.Tx != nil {
resl, e = this.Tx.Exec(query, args...)
} else {
resl, e = this.DB.Exec(query, args...)
}
this.LastErr.SetError(e)
//判断是否连接断开了
if e != nil {
if e = this.DB.Ping(); e != nil {
this.LastErr.SetError(e)
this.InitDb()
if this.LastErr.GetError() != nil {
return resl, this.LastErr
}
return this.Exec(query, args...)
}
}
return resl, this.LastErr
}
//func (this *HoTimeDB)copy(data []Map)[]Map{
// if data==nil{
// return nil
// }
//
// lth:=len(data)
//
// res:=make([]Map,lth)
//
//
// for i:=0;i<lth;i++{
//
// res[i]=DeepCopy(data[i]).(Map)
// }
//
// return res
//
//}
func (this *HoTimeDB) Select(table string, qu ...interface{}) []Map {
query := "SELECT"
where := Map{}
qs := make([]interface{}, 0)
intQs, intWhere := 0, 1
join := false
if len(qu) == 3 {
intQs = 1
intWhere = 2
join = true
}
if len(qu) > 0 {
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
query += " " + qu[intQs].(string)
} else {
for i := 0; i < len(qu[intQs].(Slice)); i++ {
k := qu[intQs].(Slice)[i].(string)
if strings.Contains(k, " AS ") {
query += " " + k + " "
} else {
query += " `" + k + "` "
}
if i+1 != len(qu[intQs].(Slice)) {
query = query + ", "
}
}
}
} else {
query += " *"
}
query += " FROM " + table
if join {
for k, v := range qu[0].(Map) {
switch Substr(k, 0, 3) {
case "[>]":
query += " LEFT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
case "[<]":
query += " RIGHT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
}
switch Substr(k, 0, 4) {
case "[<>]":
query += " FULL JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
case "[><]":
query += " INNER JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
}
}
}
if len(qu) > 1 {
where = qu[intWhere].(Map)
}
temp, resWhere := this.where(where)
query += temp
qs = append(qs, resWhere...)
md5 := this.md5(query, qs...)
if this.DBCached && this.CacheIns != nil {
//如果缓存有则从缓存取
cacheData := this.Cache(table + ":" + md5)
if cacheData.Data != nil {
return cacheData.ToMapArray()
}
}
//无缓存则数据库取
res := this.Query(query, qs...)
if res == nil {
res = []Map{}
}
//缓存
if this.DBCached && this.CacheIns != nil {
this.Cache(table+":"+md5, res)
}
return res
}
func (this *HoTimeDB) Get(table string, qu ...interface{}) Map {
//fmt.Println(qu)
if len(qu) == 1 {
qu = append(qu, Map{"LIMIT": 1})
}
if len(qu) == 2 {
temp := qu[1].(Map)
temp["LIMIT"] = 1
qu[1] = temp
}
if len(qu) == 3 {
temp := qu[2].(Map)
temp["LIMIT"] = 1
qu[2] = temp
}
//fmt.Println(qu)
data := this.Select(table, qu...)
if len(data) == 0 {
return nil
}
return data[0]
}
/**
** 计数
*/
func (this *HoTimeDB) Count(table string, qu ...interface{}) int {
req := []interface{}{}
if len(qu) == 2 {
req = append(req, qu[0])
req = append(req, "COUNT(*)")
req = append(req, qu[1])
} else {
req = append(req, "COUNT(*)")
req = append(req, qu...)
}
//req=append(req,qu...)
data := this.Select(table, req...)
//fmt.Println(data)
if len(data) == 0 {
return 0
}
//res,_:=StrToInt(data[0]["COUNT(*)"].(string))
res := ObjToStr(data[0]["COUNT(*)"])
count, _ := StrToInt(res)
return count
}
var condition = []string{"AND", "OR"}
var vcond = []string{"GROUP", "ORDER", "LIMIT"}
//where语句解析
func (this *HoTimeDB) where(data Map) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
//AND OR判断
for k, v := range data {
x := 0
for i := 0; i < len(condition); i++ {
if condition[i] == k {
tw, ts := this.cond(k, v.(Map))
where += tw
res = append(res, ts...)
break
}
x++
}
y := 0
for j := 0; j < len(vcond); j++ {
if vcond[j] == k {
break
}
y++
}
if x == len(condition) && y == len(vcond) {
tv, vv := this.varCond(k, v)
where += tv
res = append(res, vv...)
}
}
if len(where) != 0 {
where = " WHERE " + where
}
//特殊字符
for j := 0; j < len(vcond); j++ {
for k, v := range data {
if vcond[j] == k {
if k == "ORDER" {
where += " " + k + " BY "
//fmt.Println(reflect.ValueOf(v).Type())
//break
} else if k == "GROUP" {
where += " " + k + " BY "
} else {
where += " " + k
}
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
for i := 0; i < len(v.(Slice)); i++ {
where += " " + ObjToStr(v.(Slice)[i])
if len(v.(Slice)) != i+1 {
where += ","
}
}
} else {
//fmt.Println(v)
where += " " + ObjToStr(v)
}
break
}
}
}
return where, res
}
func (this *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
where := ""
res := make([]interface{}, 0)
length := len(k)
if length > 4 {
def := false
switch Substr(k, length-3, 3) {
case "[>]":
k = strings.Replace(k, "[>]", "", -1)
where += "`" + k + "`>? "
res = append(res, v)
case "[<]":
k = strings.Replace(k, "[<]", "", -1)
where += "`" + k + "`<? "
res = append(res, v)
case "[!]":
k = strings.Replace(k, "[!]", "", -1)
where, res = this.notIn(k, v, where, res)
case "[#]":
k = strings.Replace(k, "[#]", "", -1)
where += " " + k + "=" + ObjToStr(v)
case "[#!]":
k = strings.Replace(k, "[#!]", "", -1)
where += " " + k + "!=" + ObjToStr(v)
case "[!#]":
k = strings.Replace(k, "[!#]", "", -1)
where += " " + k + "!=" + ObjToStr(v)
case "[~]":
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = "%" + ObjToStr(v) + "%"
res = append(res, v)
case "[!~]": //左边任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = "%" + ObjToStr(v)
res = append(res, v)
case "[~!]": //右边任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
v = ObjToStr(v) + "%"
res = append(res, v)
case "[~~]": //手动任意
k = strings.Replace(k, "[~]", "", -1)
where += "`" + k + "` LIKE ? "
//v = ObjToStr(v)
res = append(res, v)
default:
def = true
}
if def {
switch Substr(k, length-4, 4) {
case "[>=]":
k = strings.Replace(k, "[>=]", "", -1)
where += "`" + k + "`>=? "
res = append(res, v)
case "[<=]":
k = strings.Replace(k, "[<=]", "", -1)
where += "`" + k + "`<=? "
res = append(res, v)
case "[><]":
k = strings.Replace(k, "[><]", "", -1)
where += "`" + k + "` NOT BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
res = append(res, v.(Slice)[1])
case "[<>]":
k = strings.Replace(k, "[<>]", "", -1)
where += "`" + k + "` BETWEEN ? AND ? "
res = append(res, v.(Slice)[0])
res = append(res, v.(Slice)[1])
default:
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
where += "`" + k + "` IN ("
res = append(res, (v.(Slice))...)
if len(v.(Slice)) == 0 {
where += ") "
} else {
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
}
} else {
where += "`" + k + "`=? "
res = append(res, v)
}
}
}
} else if k == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
where += " " + ObjToStr(v) + " "
} else {
//fmt.Println(reflect.ValueOf(v).Type().String())
if v == nil {
where += "`" + k + "` IS NULL"
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
//fmt.Println(v)
where += "`" + k + "` IN ("
res = append(res, (v.(Slice))...)
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
where += "`" + k + "` IN ("
res = append(res, (v.([]interface{}))...)
for i := 0; i < len(v.([]interface{})); i++ {
if i+1 != len(v.([]interface{})) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += "`" + k + "`=? "
res = append(res, v)
}
}
return where, res
}
// this.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1})
func (this *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
//where:=""
//fmt.Println(reflect.ValueOf(v).Type().String())
if v == nil {
where += "`" + k + "` IS NOT NULL "
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
where += "`" + k + "` NOT IN ("
res = append(res, (v.(Slice))...)
for i := 0; i < len(v.(Slice)); i++ {
if i+1 != len(v.(Slice)) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
where += "`" + k + "` NOT IN ("
res = append(res, (v.([]interface{}))...)
for i := 0; i < len(v.([]interface{})); i++ {
if i+1 != len(v.([]interface{})) {
where += "?,"
} else {
where += "?) "
}
//res=append(res,(v.(Slice))[i])
}
} else {
where += "`" + k + "` !=? "
res = append(res, v)
}
return where, res
}
func (this *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
where := " "
res := make([]interface{}, 0)
lens := len(data)
//fmt.Println(lens)
for k, v := range data {
x := 0
for i := 0; i < len(condition); i++ {
if condition[i] == k {
tw, ts := this.cond(k, v.(Map))
if lens--; lens <= 0 {
//fmt.Println(lens)
where += "(" + tw + ") "
} else {
where += "(" + tw + ") " + tag + " "
}
res = append(res, ts...)
break
}
x++
}
if x == len(condition) {
tv, vv := this.varCond(k, v)
res = append(res, vv...)
if lens--; lens <= 0 {
where += tv + ""
} else {
where += tv + " " + tag + " "
}
}
}
return where, res
}
//更新数据
func (this *HoTimeDB) Update(table string, data Map, where Map) int64 {
query := "UPDATE " + table + " SET "
//UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson'
qs := make([]interface{}, 0)
tp := len(data)
for k, v := range data {
vstr := "?"
if Substr(k, len(k)-3, 3) == "[#]" {
k = strings.Replace(k, "[#]", "", -1)
vstr = ObjToStr(v)
} else {
qs = append(qs, v)
}
query += "`" + k + "`=" + vstr + ""
if tp--; tp != 0 {
query += ", "
}
}
temp, resWhere := this.where(where)
//fmt.Println(resWhere)
query += temp
qs = append(qs, resWhere...)
res, err := this.Exec(query, qs...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
//如果更新成功,则删除缓存
if rows != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
return rows
}
func (this *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
query := "DELETE FROM " + table + " "
temp, resWhere := this.where(data)
query += temp
res, err := this.Exec(query, resWhere...)
rows := int64(0)
if err.GetError() == nil && res != nil {
rows, _ = res.RowsAffected()
}
//如果删除成功,删除对应缓存
if rows != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
//return 0
return rows
}
//插入新数据
func (this *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
values := make([]interface{}, 0)
queryString := " ("
valueString := " ("
lens := len(data)
tempLen := 0
for k, v := range data {
tempLen++
values = append(values, v)
if tempLen < lens {
queryString += "`" + k + "`,"
valueString += "?,"
} else {
queryString += "`" + k + "`) "
valueString += "?);"
}
}
query := "INSERT INTO " + table + queryString + "VALUES" + valueString
res, err := this.Exec(query, values...)
id := int64(0)
if err.GetError() == nil && res != nil {
id, this.LastErr.err = res.LastInsertId()
}
//如果插入成功,删除缓存
if id != 0 {
if this.DBCached && this.CacheIns != nil {
this.Cache(table+"*", nil)
}
}
//fmt.Println(id)
return id
}
-25
View File
@@ -1,25 +0,0 @@
package hotime
//框架层处理错误
type Error struct {
error
err error
}
func (this *Error) GetError() error {
return this.error
}
func (this *Error) SetError(err error, loglevel ...int) {
this.error = nil
if err == nil {
this.error = err
this.err = err
return
}
this.error = err
return
}
-382
View File
@@ -1,382 +0,0 @@
package hotime
import (
"crypto/md5"
"encoding/hex"
"fmt"
"math"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
//安全锁
//func SafeMutex(tag int, f func() interface{}) interface{} {
//
// mutexer.Lock()
// if mutex[tag] == nil {
//
// mutex[tag] = &sync.RWMutex{}
//
// }
// mutexer.Unlock()
//
// mutex[tag].Lock()
// res := f()
// mutex[tag].Unlock()
// return res
//}
func LogError(logMsg interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_ERROR)
}
func LogWarn(logMsg interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_WARN)
}
func LogInfo(logMsg ...interface{}) {
logFmt(fmt.Sprintln(logMsg), 2, LOG_INFO)
}
//func LogFmt(logMsg interface{}, loglevel ...LOG_MODE) {
//
// logFmt(logMsg, 2, loglevel...)
//}
//日志打印以及存储到文件
func logFmt(logMsg interface{}, printLevel int, loglevel ...LOG_MODE) {
if Config.GetInt("logLevel") == int(LOG_NIL) {
return
}
lev := LOG_INFO
if len(loglevel) != 0 {
lev = loglevel[0]
}
gofile := ""
if Config.GetInt("debug") != 0 && printLevel != 0 {
_, file, line, ok := runtime.Caller(printLevel)
if ok {
gofile = " " + file + ":" + ObjToStr(line)
}
}
logStr := ""
if lev == LOG_INFO {
logStr = "info:"
}
if printLevel == 0 {
logStr = "connect:"
}
if lev == LOG_WARN {
logStr = "warn:"
}
if lev == LOG_ERROR {
logStr = "error:"
}
logStr = fmt.Sprintln(time.Now().Format("2006/01/02 15:04:05"), logStr, logMsg, gofile)
//打印日志
fmt.Print(logStr)
//不需要存储到文件
if Config.GetString("logFile") == "" {
return
}
//存储到文件
logFilePath := time.Now().Format(Config.GetString("logFile"))
os.MkdirAll(filepath.Dir(logFilePath), os.ModeAppend)
//os.Create(logFilePath)
f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return
}
f.Write([]byte(logStr))
f.Close()
}
//字符串首字符大写
func StrFirstToUpper(str string) string {
if len(str) == 0 {
return str
}
first := Substr(str, 0, 1)
other := Substr(str, 1, len(str)-1)
return strings.ToUpper(first) + other
}
//字符串截取
func Substr(str string, start int, length int) string {
rs := []rune(str)
rl := len(rs)
end := 0
if start < 0 {
start = rl - 1 + start
}
end = start + length
if start > end {
start, end = end, start
}
if start < 0 {
start = 0
}
if start > rl {
start = rl
}
if end < 0 {
end = 0
}
if end > rl {
end = rl
}
return string(rs[start:end])
}
//获取最后出现字符串的下标
//return 找不到返回 -1
func IndexLastStr(str, sep string) int {
sepSlice := []rune(sep)
strSlice := []rune(str)
if len(sepSlice) > len(strSlice) {
return -1
}
v := sepSlice[len(sepSlice)-1]
for i := len(strSlice) - 1; i >= 0; i-- {
vs := strSlice[i]
if v == vs {
j := len(sepSlice) - 2
for ; j >= 0; j-- {
vj := sepSlice[j]
vsj := strSlice[i-(len(sepSlice)-j-1)]
if vj != vsj {
break
}
}
if j < 0 {
return i - len(sepSlice) + 1
}
}
}
return -1
}
//md5
func Md5(req string) string {
md5Ctx := md5.New()
md5Ctx.Write([]byte(req))
cipherStr := md5Ctx.Sum(nil)
return hex.EncodeToString(cipherStr)
}
//随机数
func Rand(count int) int {
res := Random()
for i := 0; i < count; i++ {
res = res * 10
}
return ObjToInt(res)
}
func Random() float64 {
v := float64(0)
m := float64(0.1)
for i := 0; i < 15; i++ {
facter := map[int]int{4: 1, 9: 1, 2: 1, 3: 1, 1: 1, 7: 1, 0: 1, 5: 1, 6: 1, 8: 1}
for k, _ := range facter {
v = v + float64(k)*m
break
}
m = m * 0.1
}
return v
}
//随机数范围
func RandX(small int, max int) int {
res := 0
//随机对象
if small == max {
return small
}
for {
res = ObjToInt(Random() * float64(max+1))
if res >= small {
break
}
}
return res
}
//
////路由
//func Router(ctr CtrInterface) {
//
// str := reflect.ValueOf(ctr).Type().String()
// a := strings.IndexByte(str, '.')
//
// c := len(str) - len("Ctr") - 1
//
// app := Substr(str, 0, a) //属于哪个app
// ct := Substr(str, a+1, c-a) //属于哪个控制器
// var x = map[string]CtrInterface{}
// if _, ok := Proj[app]; ok {
// //存在APP
// x = Proj[app]
// } else {
// x = map[string]CtrInterface{}
// }
// x[ct] = ctr //将控制器存入APP
// // fmt.Println(c)
// Proj[app] = x //将APP存入测试
//}
//
//func RunMethodListener(test func(app []string)) {
// RunMethodListenerFunc = test
//}
//func SetDb(db *sql.DB) {
// db.SetMaxOpenConns(2000)
// db.SetMaxIdleConns(1000)
// db.Ping()
// SqlDB = &*db
// GetDb()
//}
//复制返回数组
func DeepCopyMap(value interface{}) interface{} {
if valueMap, ok := value.(Map); ok {
newMap := make(Map)
for k, v := range valueMap {
newMap[k] = DeepCopyMap(v)
}
return newMap
} else if valueSlice, ok := value.([]interface{}); ok {
newSlice := make([]interface{}, len(valueSlice))
for k, v := range valueSlice {
newSlice[k] = DeepCopyMap(v)
}
return newSlice
} else if valueMap, ok := value.(map[string]interface{}); ok {
newMap := make(map[string]interface{})
for k, v := range valueMap {
newMap[k] = DeepCopyMap(v)
}
return newMap
} else if valueSlice, ok := value.(Slice); ok {
newSlice := make(Slice, len(valueSlice))
for k, v := range valueSlice {
newSlice[k] = DeepCopyMap(v)
}
return newSlice
}
return value
}
////获取数据库
//func GetDb() (HoTimeDB, error) {
// Db.DB = &*SqlDB
// Db.Cached = true
// return Db, nil
//}
//初始化方法
//func Init() {
//
// http.HandleFunc("/", PublicCore.myHandler)
//
// InitCache()
// http.ListenAndServe(":"+Config["port"].(string), nil)
//}
////设置Config
//func SetCfg(tData Map) {
// for k, v := range tData {
// Config[k] = v
// }
//}
//浮点数四舍五入保留小数
func Round(f float64, n int) float64 {
pow10_n := math.Pow10(n)
return math.Trunc((f+0.5/pow10_n)*pow10_n) / pow10_n
}
//func InitDbCache(tim int64) {
// res := Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + Config.GetString("dbName") + "' AND TABLE_NAME='cached'")
// if len(res) == 0 {
// Db.Exec("CREATE TABLE `cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
// }
// cacheConfig := Config.GetMap("cacheConfig").GetObj("db").(CacheConfg)
// cacheConfig.Used = true
// if tim != 0 {
// cacheConfig.Time = tim
// }
// Config.GetMap("cacheConfig")["db"] = cacheConfig
//}
//
//func Cache(key interface{}, data ...interface{}) interface{} {
// cachePRI := Config["cachePRI"].([]string)
// var res interface{}
// for i := 0; i < len(cachePRI); i++ {
// if cachePRI[i] == "ridis" && Config["cacheConfig"].(Map)["ridis"].(CacheConfg).Used {
// res = CacheMemIns.Cache(ObjToStr(key), data...)
// }
// if cachePRI[i] == "db" && Config["cacheConfig"].(Map)["db"].(CacheConfg).Used {
// res = CacheDBIns.Cache(ObjToStr(key), data...)
// }
// if cachePRI[i] == "memory" && Config["cacheConfig"].(Map)["memory"].(CacheConfg).Used {
// res = CacheMemIns.Cache(ObjToStr(key), data...)
// }
// //将缓存送到前面的可缓存仓库去
// if res != nil {
// for j := 0; j < i; j++ {
// if cachePRI[j] == "ridis" && Config["cacheConfig"].(Map)["ridis"].(CacheConfg).Used {
// CacheMemIns.Cache(ObjToStr(key), res)
// }
// if cachePRI[j] == "db" && Config["cacheConfig"].(Map)["db"].(CacheConfg).Used {
// CacheDBIns.Cache(ObjToStr(key), res)
// }
// if cachePRI[j] == "memory" && Config["cacheConfig"].(Map)["memory"].(CacheConfg).Used {
// CacheMemIns.Cache(ObjToStr(key), res)
// }
//
// }
// break
// }
//
// }
// return res
//}
//func InitCache() {
// CacheMemIns.Init(Config["cacheConfig"].(Map)["memory"].(CacheConfg).Time)
// CacheDBIns.Init(Config["cacheConfig"].(Map)["db"].(CacheConfg).Time)
//}
-155
View File
@@ -1,155 +0,0 @@
package hotime
import (
"encoding/json"
"errors"
"reflect"
)
//hotime的常用map
type Map map[string]interface{}
//获取string
func (this Map) GetString(key string, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((this)[key])
}
func (this *Map) Pointer() *Map {
return this
}
//增加接口
func (this Map) Put(key string, value interface{}) {
//if this==nil{
// this=Map{}
//}
this[key] = value
}
//删除接口
func (this Map) Delete(key string) {
delete(this, key)
}
//获取Int
func (this Map) GetInt(key string, err ...*Error) int {
v := ObjToInt((this)[key], err...)
return v
}
//获取Int
func (this Map) GetInt64(key string, err ...*Error) int64 {
v := ObjToInt64((this)[key], err...)
return v
}
//获取向上取整Int64
func (this Map) GetCeilInt64(key string, err ...*Error) int64 {
v := ObjToCeilInt64((this)[key], err...)
return v
}
//获取向上取整Int
func (this Map) GetCeilInt(key string, err ...*Error) int {
v := ObjToCeilInt((this)[key], err...)
return v
}
//获取向上取整float64
func (this Map) GetCeilFloat64(key string, err ...*Error) float64 {
v := ObjToCeilFloat64((this)[key], err...)
return v
}
//获取Float64
func (this Map) GetFloat64(key string, err ...*Error) float64 {
v := ObjToFloat64((this)[key], err...)
return v
}
func (this Map) GetSlice(key string, err ...*Error) Slice {
//var v Slice
v := ObjToSlice((this)[key], err...)
return v
}
func (this Map) GetMap(key string, err ...*Error) Map {
//var data Slice
v := ObjToMap((this)[key], err...)
return v
}
func (this Map) Get(key string, err ...*Error) interface{} {
if v, ok := (this)[key]; ok {
return v
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
//请传递指针过来
func (this Map) ToStruct(stct interface{}) {
data := reflect.ValueOf(stct).Elem()
for k, v := range this {
ks := StrFirstToUpper(k)
dkey := data.FieldByName(ks)
if !dkey.IsValid() {
continue
}
switch dkey.Type().String() {
case "int":
dkey.SetInt(this.GetInt64(k))
case "int64":
dkey.Set(reflect.ValueOf(this.GetInt64(k)))
case "float64":
dkey.Set(reflect.ValueOf(this.GetFloat64(k)))
case "string":
dkey.Set(reflect.ValueOf(this.GetString(k)))
case "interface{}":
dkey.Set(reflect.ValueOf(v))
}
}
}
func (this Map) ToJsonString() string {
return ObjToStr(this)
}
func (this Map) JsonToMap(jsonStr string, err ...*Error) {
e := json.Unmarshal([]byte(jsonStr), &this)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
}
-543
View File
@@ -1,543 +0,0 @@
package hotime
var MimeMaps = map[string]string{
".3dm": "x-world/x-3dmf",
".3dmf": "x-world/x-3dmf",
".7z": "application/x-7z-compressed",
".a": "application/octet-stream",
".aab": "application/x-authorware-bin",
".aam": "application/x-authorware-map",
".aas": "application/x-authorware-seg",
".abc": "text/vndabc",
".ace": "application/x-ace-compressed",
".acgi": "text/html",
".m3u8": "audio/mpegurl",
".afl": "video/animaflex",
".ai": "application/postscript",
".aif": "audio/aiff",
".aifc": "audio/aiff",
".aiff": "audio/aiff",
".aim": "application/x-aim",
".aip": "text/x-audiosoft-intra",
".alz": "application/x-alz-compressed",
".ani": "application/x-navi-animation",
".aos": "application/x-nokia-9000-communicator-add-on-software",
".aps": "application/mime",
".apk": "application/vnd.android.package-archive",
".arc": "application/x-arc-compressed",
".arj": "application/arj",
".art": "image/x-jg",
".asf": "video/x-ms-asf",
".asm": "text/x-asm",
".asp": "text/asp",
".asx": "application/x-mplayer2",
".au": "audio/basic",
".avi": "video/x-msvideo",
".avs": "video/avs-video",
".bcpio": "application/x-bcpio",
".bin": "application/mac-binary",
".bmp": "image/bmp",
".boo": "application/book",
".book": "application/book",
".boz": "application/x-bzip2",
".bsh": "application/x-bsh",
".bz2": "application/x-bzip2",
".bz": "application/x-bzip",
".c++": "text/plain",
".c": "text/x-c",
".cab": "application/vnd.ms-cab-compressed",
".cat": "application/vndms-pkiseccat",
".cc": "text/x-c",
".ccad": "application/clariscad",
".cco": "application/x-cocoa",
".cdf": "application/cdf",
".cer": "application/pkix-cert",
".cha": "application/x-chat",
".chat": "application/x-chat",
".chrt": "application/vnd.kde.kchart",
".class": "application/java",
".com": "text/plain",
".conf": "text/plain",
".cpio": "application/x-cpio",
".cpp": "text/x-c",
".cpt": "application/mac-compactpro",
".crl": "application/pkcs-crl",
".crt": "application/pkix-cert",
".crx": "application/x-chrome-extension",
".csh": "text/x-scriptcsh",
".css": "text/css;charset=utf-8",
".csv": "text/csv",
".cxx": "text/plain",
".dar": "application/x-dar",
".dcr": "application/x-director",
".deb": "application/x-debian-package",
".deepv": "application/x-deepv",
".def": "text/plain",
".der": "application/x-x509-ca-cert",
".dif": "video/x-dv",
".dir": "application/x-director",
".divx": "video/divx",
".dl": "video/dl",
".dmg": "application/x-apple-diskimage",
".doc": "application/msword",
".dot": "application/msword",
".dp": "application/commonground",
".drw": "application/drafting",
".dump": "application/octet-stream",
".dv": "video/x-dv",
".dvi": "application/x-dvi",
".dwf": "drawing/x-dwf=(old)",
".dwg": "application/acad",
".dxf": "application/dxf",
".dxr": "application/x-director",
".el": "text/x-scriptelisp",
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
".eml": "message/rfc822",
".env": "application/x-envoy",
".eps": "application/postscript",
".es": "application/x-esrehber",
".etx": "text/x-setext",
".evy": "application/envoy",
".exe": "application/octet-stream",
".f77": "text/x-fortran",
".f90": "text/x-fortran",
".f": "text/x-fortran",
".fdf": "application/vndfdf",
".fif": "application/fractals",
".fli": "video/fli",
".flo": "image/florian",
".flv": "video/x-flv",
".flx": "text/vndfmiflexstor",
".fmf": "video/x-atomic3d-feature",
".for": "text/x-fortran",
".fpx": "image/vndfpx",
".frl": "application/freeloader",
".funk": "audio/make",
".g3": "image/g3fax",
".g": "text/plain",
".gif": "image/gif",
".gl": "video/gl",
".gsd": "audio/x-gsm",
".gsm": "audio/x-gsm",
".gsp": "application/x-gsp",
".gss": "application/x-gss",
".gtar": "application/x-gtar",
".gz": "application/x-compressed",
".gzip": "application/x-gzip",
".h": "text/x-h",
".hdf": "application/x-hdf",
".help": "application/x-helpfile",
".hgl": "application/vndhp-hpgl",
".hh": "text/x-h",
".hlb": "text/x-script",
".hlp": "application/hlp",
".hpg": "application/vndhp-hpgl",
".hpgl": "application/vndhp-hpgl",
".hqx": "application/binhex",
".hta": "application/hta",
".htc": "text/x-component",
".htm": "text/html;charset=utf-8",
".html": "text/html;charset=utf-8",
".htmls": "text/html",
".htt": "text/webviewhtml",
".htx": "text/html",
".ice": "x-conference/x-cooltalk",
".ico": "image/x-icon",
".ics": "text/calendar",
".icz": "text/calendar",
".idc": "text/plain",
".ief": "image/ief",
".iefs": "image/ief",
".iges": "application/iges",
".igs": "application/iges",
".ima": "application/x-ima",
".imap": "application/x-httpd-imap",
".inf": "application/inf",
".ins": "application/x-internett-signup",
".ip": "application/x-ip2",
".isu": "video/x-isvideo",
".it": "audio/it",
".iv": "application/x-inventor",
".ivr": "i-world/i-vrml",
".ivy": "application/x-livescreen",
".jam": "audio/x-jam",
".jav": "text/x-java-source",
".java": "text/x-java-source",
".jcm": "application/x-java-commerce",
".jfif-tbnl": "image/jpeg",
".jfif": "image/jpeg",
".jnlp": "application/x-java-jnlp-file",
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".jps": "image/x-jps",
".js": "application/javascript;charset=utf-8",
".json": "application/json",
".jut": "image/jutvision",
".kar": "audio/midi",
".karbon": "application/vnd.kde.karbon",
".kfo": "application/vnd.kde.kformula",
".flw": "application/vnd.kde.kivio",
".kml": "application/vnd.google-earth.kml+xml",
".kmz": "application/vnd.google-earth.kmz",
".kon": "application/vnd.kde.kontour",
".kpr": "application/vnd.kde.kpresenter",
".kpt": "application/vnd.kde.kpresenter",
".ksp": "application/vnd.kde.kspread",
".kwd": "application/vnd.kde.kword",
".kwt": "application/vnd.kde.kword",
".ksh": "text/x-scriptksh",
".la": "audio/nspaudio",
".lam": "audio/x-liveaudio",
".latex": "application/x-latex",
".lha": "application/lha",
".lhx": "application/octet-stream",
".list": "text/plain",
".lma": "audio/nspaudio",
".log": "text/plain",
".lsp": "text/x-scriptlisp",
".lst": "text/plain",
".lsx": "text/x-la-asf",
".ltx": "application/x-latex",
".lzh": "application/octet-stream",
".lzx": "application/lzx",
".m1v": "video/mpeg",
".m2a": "audio/mpeg",
".m2v": "video/mpeg",
".m3u": "audio/x-mpegurl",
".m": "text/x-m",
".man": "application/x-troff-man",
".manifest": "text/cache-manifest",
".map": "application/x-navimap",
".mar": "text/plain",
".mbd": "application/mbedlet",
".mc$": "application/x-magic-cap-package-10",
".mcd": "application/mcad",
".mcf": "text/mcf",
".mcp": "application/netmc",
".me": "application/x-troff-me",
".mht": "message/rfc822",
".mhtml": "message/rfc822",
".mid": "application/x-midi",
".midi": "application/x-midi",
".mif": "application/x-frame",
".mime": "message/rfc822",
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
".mjpg": "video/x-motion-jpeg",
".mm": "application/base64",
".mme": "application/base64",
".mod": "audio/mod",
".moov": "video/quicktime",
".mov": "video/quicktime",
".movie": "video/x-sgi-movie",
".mp2": "audio/mpeg",
".mp3": "audio/mpeg3",
".mp4": "video/mp4",
".mpa": "audio/mpeg",
".mpc": "application/x-project",
".mpe": "video/mpeg",
".mpeg": "video/mpeg",
".mpg": "video/mpeg",
".mpga": "audio/mpeg",
".mpp": "application/vndms-project",
".mpt": "application/x-project",
".mpv": "application/x-project",
".mpx": "application/x-project",
".mrc": "application/marc",
".ms": "application/x-troff-ms",
".mv": "video/x-sgi-movie",
".my": "audio/make",
".mzz": "application/x-vndaudioexplosionmzz",
".nap": "image/naplps",
".naplps": "image/naplps",
".nc": "application/x-netcdf",
".ncm": "application/vndnokiaconfiguration-message",
".nif": "image/x-niff",
".niff": "image/x-niff",
".nix": "application/x-mix-transfer",
".nsc": "application/x-conference",
".nvd": "application/x-navidoc",
".o": "application/octet-stream",
".oda": "application/oda",
".odb": "application/vnd.oasis.opendocument.database",
".odc": "application/vnd.oasis.opendocument.chart",
".odf": "application/vnd.oasis.opendocument.formula",
".odg": "application/vnd.oasis.opendocument.graphics",
".odi": "application/vnd.oasis.opendocument.image",
".odm": "application/vnd.oasis.opendocument.text-master",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogg": "audio/ogg",
".ogv": "video/ogg",
".omc": "application/x-omc",
".omcd": "application/x-omcdatamaker",
".omcr": "application/x-omcregerator",
".otc": "application/vnd.oasis.opendocument.chart-template",
".otf": "application/vnd.oasis.opendocument.formula-template",
".otg": "application/vnd.oasis.opendocument.graphics-template",
".oth": "application/vnd.oasis.opendocument.text-web",
".oti": "application/vnd.oasis.opendocument.image-template",
".otm": "application/vnd.oasis.opendocument.text-master",
".otp": "application/vnd.oasis.opendocument.presentation-template",
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
".ott": "application/vnd.oasis.opendocument.text-template",
".p10": "application/pkcs10",
".p12": "application/pkcs-12",
".p7a": "application/x-pkcs7-signature",
".p7c": "application/pkcs7-mime",
".p7m": "application/pkcs7-mime",
".p7r": "application/x-pkcs7-certreqresp",
".p7s": "application/pkcs7-signature",
".p": "text/x-pascal",
".part": "application/pro_eng",
".pas": "text/pascal",
".pbm": "image/x-portable-bitmap",
".pcl": "application/vndhp-pcl",
".pct": "image/x-pict",
".pcx": "image/x-pcx",
".pdb": "chemical/x-pdb",
".pdf": "application/pdf",
".pfunk": "audio/make",
".pgm": "image/x-portable-graymap",
".pic": "image/pict",
".pict": "image/pict",
".pkg": "application/x-newton-compatible-pkg",
".pko": "application/vndms-pkipko",
".pl": "text/x-scriptperl",
".plx": "application/x-pixclscript",
".pm4": "application/x-pagemaker",
".pm5": "application/x-pagemaker",
".pm": "text/x-scriptperl-module",
".png": "image/png",
".pnm": "application/x-portable-anymap",
".pot": "application/mspowerpoint",
".pov": "model/x-pov",
".ppa": "application/vndms-powerpoint",
".ppm": "image/x-portable-pixmap",
".pps": "application/mspowerpoint",
".ppt": "application/mspowerpoint",
".ppz": "application/mspowerpoint",
".pre": "application/x-freelance",
".prt": "application/pro_eng",
".ps": "application/postscript",
".psd": "application/octet-stream",
".pvu": "paleovu/x-pv",
".pwz": "application/vndms-powerpoint",
".py": "text/x-scriptphyton",
".pyc": "applicaiton/x-bytecodepython",
".qcp": "audio/vndqcelp",
".qd3": "x-world/x-3dmf",
".qd3d": "x-world/x-3dmf",
".qif": "image/x-quicktime",
".qt": "video/quicktime",
".qtc": "video/x-qtc",
".qti": "image/x-quicktime",
".qtif": "image/x-quicktime",
".ra": "audio/x-pn-realaudio",
".ram": "audio/x-pn-realaudio",
".rar": "application/x-rar-compressed",
".ras": "application/x-cmu-raster",
".rast": "image/cmu-raster",
".rexx": "text/x-scriptrexx",
".rf": "image/vndrn-realflash",
".rgb": "image/x-rgb",
".rm": "application/vndrn-realmedia",
".rmi": "audio/mid",
".rmm": "audio/x-pn-realaudio",
".rmp": "audio/x-pn-realaudio",
".rng": "application/ringing-tones",
".rnx": "application/vndrn-realplayer",
".roff": "application/x-troff",
".rp": "image/vndrn-realpix",
".rpm": "audio/x-pn-realaudio-plugin",
".rt": "text/vndrn-realtext",
".rtf": "text/richtext",
".rtx": "text/richtext",
".rv": "video/vndrn-realvideo",
".s": "text/x-asm",
".s3m": "audio/s3m",
".s7z": "application/x-7z-compressed",
".saveme": "application/octet-stream",
".sbk": "application/x-tbook",
".scm": "text/x-scriptscheme",
".sdml": "text/plain",
".sdp": "application/sdp",
".sdr": "application/sounder",
".sea": "application/sea",
".set": "application/set",
".sgm": "text/x-sgml",
".sgml": "text/x-sgml",
".sh": "text/x-scriptsh",
".shar": "application/x-bsh",
".shtml": "text/x-server-parsed-html",
".sid": "audio/x-psid",
".skd": "application/x-koan",
".skm": "application/x-koan",
".skp": "application/x-koan",
".skt": "application/x-koan",
".sit": "application/x-stuffit",
".sitx": "application/x-stuffitx",
".sl": "application/x-seelogo",
".smi": "application/smil",
".smil": "application/smil",
".snd": "audio/basic",
".sol": "application/solids",
".spc": "text/x-speech",
".spl": "application/futuresplash",
".spr": "application/x-sprite",
".sprite": "application/x-sprite",
".spx": "audio/ogg",
".src": "application/x-wais-source",
".ssi": "text/x-server-parsed-html",
".ssm": "application/streamingmedia",
".sst": "application/vndms-pkicertstore",
".step": "application/step",
".stl": "application/sla",
".stp": "application/step",
".sv4cpio": "application/x-sv4cpio",
".sv4crc": "application/x-sv4crc",
".svf": "image/vnddwg",
".svg": "image/svg+xml",
".svr": "application/x-world",
".swf": "application/x-shockwave-flash",
".t": "application/x-troff",
".talk": "text/x-speech",
".tar": "application/x-tar",
".tbk": "application/toolbook",
".tcl": "text/x-scripttcl",
".tcsh": "text/x-scripttcsh",
".tex": "application/x-tex",
".texi": "application/x-texinfo",
".texinfo": "application/x-texinfo",
".text": "text/plain",
".tgz": "application/gnutar",
".tif": "image/tiff",
".tiff": "image/tiff",
".tr": "application/x-troff",
".tsi": "audio/tsp-audio",
".tsp": "application/dsptype",
".tsv": "text/tab-separated-values",
".turbot": "image/florian",
".txt": "text/plain",
".uil": "text/x-uil",
".uni": "text/uri-list",
".unis": "text/uri-list",
".unv": "application/i-deas",
".uri": "text/uri-list",
".uris": "text/uri-list",
".ustar": "application/x-ustar",
".uu": "text/x-uuencode",
".uue": "text/x-uuencode",
".vcd": "application/x-cdlink",
".vcf": "text/x-vcard",
".vcard": "text/x-vcard",
".vcs": "text/x-vcalendar",
".vda": "application/vda",
".vdo": "video/vdo",
".vew": "application/groupwise",
".viv": "video/vivo",
".vivo": "video/vivo",
".vmd": "application/vocaltec-media-desc",
".vmf": "application/vocaltec-media-file",
".voc": "audio/voc",
".vos": "video/vosaic",
".vox": "audio/voxware",
".vqe": "audio/x-twinvq-plugin",
".vqf": "audio/x-twinvq",
".vql": "audio/x-twinvq-plugin",
".vrml": "application/x-vrml",
".vrt": "x-world/x-vrt",
".vsd": "application/x-visio",
".vst": "application/x-visio",
".vsw": "application/x-visio",
".w60": "application/wordperfect60",
".w61": "application/wordperfect61",
".w6w": "application/msword",
".wav": "audio/wav",
".wb1": "application/x-qpro",
".wbmp": "image/vnd.wap.wbmp",
".web": "application/vndxara",
".wiz": "application/msword",
".wk1": "application/x-123",
".wmf": "windows/metafile",
".wml": "text/vnd.wap.wml",
".wmlc": "application/vnd.wap.wmlc",
".wmls": "text/vnd.wap.wmlscript",
".wmlsc": "application/vnd.wap.wmlscriptc",
".word": "application/msword",
".wp5": "application/wordperfect",
".wp6": "application/wordperfect",
".wp": "application/wordperfect",
".wpd": "application/wordperfect",
".wq1": "application/x-lotus",
".wri": "application/mswrite",
".wrl": "application/x-world",
".wrz": "model/vrml",
".wsc": "text/scriplet",
".wsrc": "application/x-wais-source",
".wtk": "application/x-wintalk",
".x-png": "image/png",
".xbm": "image/x-xbitmap",
".xdr": "video/x-amt-demorun",
".xgz": "xgl/drawing",
".xif": "image/vndxiff",
".xl": "application/excel",
".xla": "application/excel",
".xlb": "application/excel",
".xlc": "application/excel",
".xld": "application/excel",
".xlk": "application/excel",
".xll": "application/excel",
".xlm": "application/excel",
".xls": "application/excel",
".xlt": "application/excel",
".xlv": "application/excel",
".xlw": "application/excel",
".xm": "audio/xm",
".xml": "text/xml",
".xmz": "xgl/movie",
".xpix": "application/x-vndls-xpix",
".xpm": "image/x-xpixmap",
".xsr": "video/x-amt-showrun",
".xwd": "image/x-xwd",
".xyz": "chemical/x-pdb",
".z": "application/x-compress",
".zip": "application/zip",
".zoo": "application/octet-stream",
".zsh": "text/x-scriptzsh",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".docm": "application/vnd.ms-word.document.macroEnabled.12",
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
".thmx": "application/vnd.ms-officetheme",
".onetoc": "application/onenote",
".onetoc2": "application/onenote",
".onetmp": "application/onenote",
".onepkg": "application/onenote",
".key": "application/x-iwork-keynote-sffkey",
".kth": "application/x-iwork-keynote-sffkth",
".nmbtemplate": "application/x-iwork-numbers-sfftemplate",
".numbers": "application/x-iwork-numbers-sffnumbers",
".pages": "application/x-iwork-pages-sffpages",
".template": "application/x-iwork-pages-sfftemplate",
".xpi": "application/x-xpinstall",
".oex": "application/x-opera-extension",
".mustache": "text/html",
}
-93
View File
@@ -1,93 +0,0 @@
package hotime
//对象封装方便取用
type Obj struct {
Data interface{}
contextBase
}
func (this *Obj) Put(data interface{}) {
this.Data = data
}
func (this *Obj) ToInt(err ...Error) int {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToInt(this.Data, &this.Error)
}
func (this *Obj) ToInt64(err ...Error) int64 {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToInt64(this.Data, &this.Error)
}
func (this *Obj) ToFloat64(err ...Error) float64 {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToFloat64(this.Data, &this.Error)
}
//获取向上取整float64
func (this *Obj) ToCeilFloat64(err ...*Error) float64 {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilFloat64(this.Data, err...)
return v
}
func (this *Obj) ToStr() string {
return ObjToStr(this.Data)
}
func (this *Obj) ToMap(err ...Error) Map {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToMap(this.Data, &this.Error)
}
func (this *Obj) ToSlice(err ...Error) Slice {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToSlice(this.Data, &this.Error)
}
func (this *Obj) ToMapArray(err ...Error) []Map {
if len(err) != 0 {
this.Error = err[0]
}
return ObjToMapArray(this.Data, &this.Error)
}
func (this *Obj) ToObj() interface{} {
return this.Data
}
//获取向上取整Int64
func (this *Obj) ToCeilInt64(err ...*Error) int64 {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilInt64(this.Data, err...)
return v
}
//获取向上取整Int
func (this *Obj) ToCeilInt(err ...*Error) int {
if len(err) != 0 {
this.Error = *err[0]
}
v := ObjToCeilInt(this.Data, err...)
return v
}
-294
View File
@@ -1,294 +0,0 @@
package hotime
import (
"encoding/json"
"errors"
"math"
"strconv"
)
//仅限于hotime.Slice
func ObjToMap(obj interface{}, e ...*Error) Map {
var err error
var v Map
if obj == nil {
v = nil
err = errors.New("没有合适的转换对象!")
} else {
switch obj.(type) {
case Map:
v = obj.(Map)
case map[string]interface{}:
v = obj.(map[string]interface{})
case string:
v = Map{}
e := json.Unmarshal([]byte(obj.(string)), &v)
if e != nil {
err = errors.New("没有合适的转换对象!" + e.Error())
v = nil
}
default:
data, err := json.Marshal(obj)
if err != nil {
err = errors.New("没有合适的转换对象!" + err.Error())
v = nil
}
v = Map{}
e := json.Unmarshal(data, &v)
if e != nil {
err = errors.New("没有合适的转换对象!" + e.Error())
v = nil
}
}
}
if len(e) != 0 {
e[0].SetError(err)
}
return v
}
func ObjToMapArray(obj interface{}, e ...*Error) []Map {
s := ObjToSlice(obj, e...)
res := []Map{}
for i := 0; i < len(s); i++ {
res = append(res, s.GetMap(i))
}
return res
}
//仅限于hotime.Slice
func ObjToSlice(obj interface{}, e ...*Error) Slice {
var err error
var v Slice
if obj == nil {
v = nil
err = errors.New("没有合适的转换对象!")
} else {
switch obj.(type) {
case Slice:
v = obj.(Slice)
case []interface{}:
v = obj.([]interface{})
case []string:
v = Slice{}
for i := 0; i < len(obj.([]string)); i++ {
v = append(v, obj.([]string)[i])
}
case string:
v = Slice{}
err = json.Unmarshal([]byte(obj.(string)), &v)
default:
v = Slice{}
var data []byte
data, err = json.Marshal(obj)
err = json.Unmarshal(data, &v)
}
}
if len(e) != 0 {
e[0].SetError(err)
}
return v
}
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
var err error
v := float64(0)
if obj == nil {
err = errors.New("没有合适的转换对象!")
} else {
switch obj.(type) {
case int:
v = float64(obj.(int))
case int64:
v = float64(obj.(int64))
case string:
value, e := strconv.ParseFloat(obj.(string), 64)
if e != nil {
v = float64(0)
err = e
} else {
v = value
}
case float64:
v = obj.(float64)
case float32:
v = float64(obj.(float32))
case uint8:
value, e := strconv.ParseFloat(obj.(string), 64)
if e != nil {
v = float64(0)
err = e
} else {
v = value
}
default:
v = float64(0)
err = errors.New("没有合适的转换对象!")
}
}
if len(e) != 0 {
e[0].SetError(err)
}
return v
}
//向上取整
func ObjToCeilInt64(obj interface{}, e ...*Error) int64 {
f := ObjToCeilFloat64(obj, e...)
return ObjToInt64(math.Ceil(f))
}
//向上取整
func ObjToCeilFloat64(obj interface{}, e ...*Error) float64 {
f := ObjToFloat64(obj, e...)
return math.Ceil(f)
}
//向上取整
func ObjToCeilInt(obj interface{}, e ...*Error) int {
f := ObjToCeilFloat64(obj, e...)
return ObjToInt(f)
}
func ObjToInt64(obj interface{}, e ...*Error) int64 {
var err error
v := int64(0)
if obj == nil {
err = errors.New("没有合适的转换对象!")
} else {
switch obj.(type) {
case int:
v = int64(obj.(int))
case int64:
v = obj.(int64)
case string:
value, e := StrToInt(obj.(string))
if e != nil {
v = int64(0)
err = e
} else {
v = int64(value)
}
case uint8:
value, e := StrToInt(obj.(string))
if e != nil {
v = int64(0)
err = e
} else {
v = int64(value)
}
case float64:
v = int64(obj.(float64))
case float32:
v = int64(obj.(float32))
default:
v = int64(0)
err = errors.New("没有合适的转换对象!")
}
}
if len(e) != 0 {
e[0].SetError(err)
}
return v
}
func ObjToInt(obj interface{}, e ...*Error) int {
v := ObjToInt64(obj, e...)
return int(v)
}
func ObjToStr(obj interface{}) string {
// fmt.Println(reflect.ValueOf(obj).Type().String() )
str := ""
if obj == nil {
return str
}
switch obj.(type) {
case int:
str = strconv.Itoa(obj.(int))
case uint8:
str = obj.(string)
case int64:
str = strconv.FormatInt(obj.(int64), 10)
case []byte:
str = string(obj.([]byte))
case string:
str = obj.(string)
case float64:
str = strconv.FormatFloat(obj.(float64), 'f', -1, 64)
default:
strbte, err := json.Marshal(obj)
if err == nil {
str = string(strbte)
}
}
return str
}
//转换为Map
func StrToMap(string string) Map {
data := Map{}
data.JsonToMap(string)
return data
}
//转换为Slice
func StrToSlice(string string) Slice {
data := ObjToSlice(string)
return data
}
//字符串数组: a1,a2,a3转["a1","a2","a3"]
func StrArrayToJsonStr(a string) string {
if len(a) > 2 {
if a[0] == ',' {
a = Substr(a, 1, len(a)-1)
}
if a[len(a)-1] == ',' {
a = Substr(a, 0, len(a)-1)
}
//a = strings.Replace(a, ",", `,`, -1)
a = `[` + a + `]`
} else {
a = "[]"
}
return a
}
//字符串数组: a1,a2,a3转["a1","a2","a3"]
func JsonStrToStrArray(a string) string {
//a = strings.Replace(a, `"`, "", -1)
if len(a) != 0 {
a = Substr(a, 1, len(a)-2)
}
return "," + a + ","
}
//字符串转int
func StrToInt(s string) (int, error) {
i, err := strconv.Atoi(s)
return i, err
}
-71
View File
@@ -1,71 +0,0 @@
package hotime
//session对象
type SessionIns struct {
ShortCache CacheIns
LongCache CacheIns
SessionId string
Map
contextBase
}
func (this *SessionIns) set() {
if this.ShortCache != nil {
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
if this.LongCache != nil {
this.LongCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
}
func (this *SessionIns) Session(key string, data ...interface{}) *Obj {
if this.Map == nil {
this.get()
}
if len(data) != 0 {
if data[0] == nil {
delete(this.Map, key)
this.set()
} else {
this.Map[key] = data[0]
this.set()
}
return &Obj{Data: nil}
}
return &Obj{Data: this.Map.Get(key)}
}
func (this *SessionIns) get() {
if this.ShortCache != nil {
this.Map = this.ShortCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
if this.Map != nil {
return
}
}
if this.LongCache != nil {
this.Map = this.LongCache.Cache(HEAD_SESSION_ADD + this.SessionId).ToMap()
if this.Map != nil {
if this.ShortCache != nil {
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
}
return
}
}
this.Map = Map{}
this.ShortCache.Cache(HEAD_SESSION_ADD+this.SessionId, this.Map)
return
}
func (this *SessionIns) Init(short CacheIns, long CacheIns) {
this.ShortCache = short
this.LongCache = long
}
-87
View File
@@ -1,87 +0,0 @@
package hotime
import (
"errors"
)
type Slice []interface{}
//获取string
func (this Slice) GetString(key int, err ...*Error) string {
if len(err) != 0 {
err[0].SetError(nil)
}
return ObjToStr((this)[key])
}
//获取Int
func (this Slice) GetInt(key int, err ...*Error) int {
v := ObjToInt((this)[key], err...)
return v
}
//获取Int
func (this Slice) GetInt64(key int, err ...*Error) int64 {
v := ObjToInt64((this)[key], err...)
return v
}
//获取向上取整Int64
func (this Slice) GetCeilInt64(key int, err ...*Error) int64 {
v := ObjToCeilInt64((this)[key], err...)
return v
}
//获取向上取整Int
func (this Slice) GetCeilInt(key int, err ...*Error) int {
v := ObjToCeilInt((this)[key], err...)
return v
}
//获取向上取整float64
func (this Slice) GetCeilFloat64(key int, err ...*Error) float64 {
v := ObjToCeilFloat64((this)[key], err...)
return v
}
//获取Float64
func (this Slice) GetFloat64(key int, err ...*Error) float64 {
v := ObjToFloat64((this)[key], err...)
return v
}
func (this Slice) GetSlice(key int, err ...*Error) Slice {
v := ObjToSlice((this)[key], err...)
return v
}
func (this Slice) GetMap(key int, err ...*Error) Map {
//var v Map
v := ObjToMap((this)[key], err...)
return v
}
func (this Slice) Get(key int, err ...*Error) interface{} {
if key < len(this) {
return this[key]
}
e := errors.New("没有存储key及对应的数据")
if len(err) != 0 {
err[0].SetError(e)
}
return nil
}
func (this Slice) Put(key int, value interface{}) {
this[key] = value
}
func (this Slice) ToJsonString() string {
return ObjToStr(this)
}
-18
View File
@@ -1,18 +0,0 @@
package db
import (
"code.hoteas.com/golang/hotime"
"code.hoteas.com/golang/hotime/tools/mysql"
"code.hoteas.com/golang/hotime/tools/sqlite"
"strings"
)
func SetDB(appIns *hotime.Application) {
if appIns.Config.GetString("dbType") == "sqlite" || strings.Index(appIns.Config.GetString("dbName"), ".db") == len(appIns.Config.GetString("dbName"))-3 {
appIns.Config["dbType"] = "sqlite"
sqlite.SetDB(appIns)
} else {
appIns.Config["dbType"] = "mysql"
mysql.SetDB(appIns)
}
}
-19
View File
@@ -1,19 +0,0 @@
package mysql
import (
"code.hoteas.com/golang/hotime"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func SetDB(appIns *hotime.Application) {
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
query := appIns.Config.GetString("dbUser") + ":" + appIns.Config.GetString("dbPwd") +
"@tcp(" + appIns.Config.GetString("dbHost") + ":" + appIns.Config.GetString("dbPort") + ")/" + appIns.Config.GetString("dbName") + "?charset=utf8"
DB, e := sql.Open("mysql", query)
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
return DB
})
}
-17
View File
@@ -1,17 +0,0 @@
package sqlite
import (
"code.hoteas.com/golang/hotime"
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func SetDB(appIns *hotime.Application) {
appIns.SetConnectDB(func(err ...*hotime.Error) *sql.DB {
db, e := sql.Open("sqlite3", appIns.Config.GetString("dbName"))
if e != nil && len(err) != 0 {
err[0].SetError(e)
}
return db
})
}
-70
View File
@@ -1,70 +0,0 @@
package hotime
var IsRun = false //当前状态
var App = map[string]*Application{} //整个项目
//var Db = HoTimeDB{} //数据库实例
var Config = Map{
"debug": 1, //debug 0关闭1开启
"logLevel": LOG_FMT,
"dbHost": "127.0.0.1",
"dbName": "test",
"dbUser": "root",
"dbPwd": "root",
"dbPort": "3306",
"dbCached": 0, //0不开启缓存
"cacheShortTime": 60 * 60 * 2,
"cacheLongTime": 60 * 60 * 24 * 30,
"error": Map{
"1": "内部系统异常",
"2": "访问权限异常",
"3": "请求参数异常",
"4": "数据处理异常",
"5": "数据结果异常",
},
"tpt": "tpt",
"defFile": []string{"index.html", "index.htm"},
"modeRouterStrict": false, //路由严格模式/a/b/c
"port": "80",
"sessionName": "HOTIME",
"tlsPort": "0",
"tlsKey": "",
"tlsCert": "",
}
var ConfigNote = Map{
"logLevel": "日志等级,0关闭,1打印",
"logFile": "如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
"debug": "是否开启debug模式,0关闭,其他开启,debug模式下日志展示更全", //debug 0关闭1开启
"dbHost": "数据库ip地址默认127.0.0.1",
"dbName": "数据库名称,sqlite为文件路径比如a/x.db",
"dbUser": "数据库用户名",
"dbPwd": "数据库密码",
"dbPort": "数据库端口",
"dbType": "如果需要使用自动数据库配置,请设置此项,手动配置数据库不需要,目前支持mysql,sqlite",
"dbCached": "是否开启数据库缓存0为关闭,其他开启", //0不开启缓存
"redisHost": "如果需要使用redis服务时配置,默认服务ip127.0.0.1",
"redisPort": "如果需要使用redis服务时配置,默认服务端口:6379",
"redisPwd": "如果需要使用redis服务时配置,默认服务密码:123456",
"cacheShortTime": "两级缓存,短缓存存储时间60 * 60 * 2,一般为内存缓存",
"cacheLongTime": "两级缓存,长缓存存储时间60 * 60 * 24 * 30,一般为数据库或者redis缓存",
"error": Map{
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
"2": "访问权限异常,没有登录或者登录异常等时候使用",
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
"注释": "web服务内置错误提示,自定义异常建议10开始",
},
"tpt": "静态文件目录,默认为程序目录下tpt目录",
"defFile": "默认访问文件,默认访问index.html或者index.htm文件",
"crossDomain": "如果需要跨域设置,空字符串为不开启,*为开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域", //是否开启跨域
"modeRouterStrict": "路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配", //路由严格模式/a/b/c
"connectLogShow": "如果需要web访问链接、访问ip、访问时间打印,0为关闭其他数字开启此功能",
"sessionName": "设置session的cookie名默认HOTIME",
"port": "web服务开启Http端口,0为不启用http服务,默认80",
"tlsPort": "web服务https端口,0为不启用https服务",
"tlsKey": "https密钥",
"tlsCert": "https证书",
}
@@ -0,0 +1 @@
test/Test*.xlsx
+26
View File
@@ -0,0 +1,26 @@
language: go
install:
- go get -d -t -v ./... && go build -v ./...
go:
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
os:
- linux
- osx
env:
matrix:
- GOARCH=amd64
- GOARCH=386
script:
- go vet ./...
- go test ./... -v -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
+46
View File
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xuri.me](https://xuri.me). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+464
View File
@@ -0,0 +1,464 @@
<!-- use this template to generate the contributor docs with the following command: `$ lingo run docs --template CONTRIBUTING_TEMPLATE.md --output CONTRIBUTING.md` -->
# Contributing to excelize
Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#community-guidelines) before you
start participating.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#community-guidelines)
## Reporting security issues
The excelize maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[xuri.me](https://xuri.me).
Security reports are greatly appreciated and we will publicly thank you for it.
We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [our issue database](https://github.com/360EntSecGroup-Skylar/excelize/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, you can use the "subscribe" button to get notified on
updates. Do *not* leave random "+1" or "I have this too" comments, as they
only clutter the discussion, and don't help resolving it. However, if you
have ways to reproduce the issue or have additional information that may help
resolving the issue, please leave a comment.
When reporting issues, always include the output of `go env`.
Also include the steps required to reproduce the problem if possible and
applicable. This information will help us review and fix your issue faster.
When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com).
Don't forget to remove sensitive data from your logfiles before posting (you can
replace those parts with "REDACTED").
## Quick contribution tips and guidelines
This section gives the experienced contributor some tips and guidelines.
### Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it. Any significant improvement should be
documented as [a GitHub issue](https://github.com/360EntSecGroup-Skylar/excelize/issues) before
anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged!
### Design and cleanup proposals
You can propose new designs for existing excelize features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project.
We try hard to keep excelize lean and focused. Excelize can't do everything for
everybody. This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* excelize.
### Conventions
Fork the repository and make changes on your fork in a feature branch:
* If it's a bug fix branch, name it XXXX-something where XXXX is the number of
the issue.
* If it's a feature branch, create an enhancement issue to announce
your intentions, and name it XXXX-something where XXXX is the number of the
issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test on your branch before
submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
### Successful Changes
Before contributing large or high impact changes, make the effort to coordinate
with the maintainers of the project before submitting a pull request. This
prevents you from doing extra work that may or may not be merged.
Large PRs that are just submitted without any prior communication are unlikely
to be successful.
While pull requests are the methodology for submitting changes to code, changes
are much more likely to be accepted if they are accompanied by additional
engineering work. While we don't define this explicitly, most of these goals
are accomplished through communication of the design goals and subsequent
solutions. Often times, it helps to first state the problem before presenting
solutions.
Typically, the best methods of accomplishing this are to submit an issue,
stating the problem. This issue can include a problem statement and a
checklist with requirements. If solutions are proposed, alternatives should be
listed and eliminated. Even if the criteria for elimination of a solution is
frivolous, say so.
Larger changes typically work best with design documents. These are focused on
providing context to the design at the time the feature was conceived and can
inform future documentation contributions.
### Commit Messages
Commit messages must start with a capitalized and short summary
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Commit messages should follow best practices, including explaining the context
of the problem and how it was solved, including in caveats or follow up changes
required. They should tell the story of the change and provide readers
understanding of what led to it.
In practice, the best approach to maintaining a nice commit message is to
leverage a `git add -p` and `git commit --amend` to formulate a solid
changeset. This allows one to piece together a change, as information becomes
available.
If you squash a series of commits, don't just submit that. Re-write the commit
message, as if the series of commits was a single stroke of brilliance.
That said, there is no requirement to have a single commit for a PR, as long as
each commit tells the story. For example, if there is a feature that requires a
package, it might make sense to have the package in a separate commit then have
a subsequent commit that uses it.
Remember, you're telling part of the story with the commit message. Don't make
your chapter weird.
### Review
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
Pull requests must be cleanly rebased on top of master without multiple branches
mixed into the PR.
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
After every commit, make sure the test passes. Include documentation
changes in the same pull request so that a revert would remove all traces of
the feature or fix.
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
close an issue. Including references automatically closes the issue on a merge.
Please see the [Coding Style](#coding-style) for further guidelines.
### Merge approval
The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance.
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```text
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Ri Xu https://xuri.me
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
### How can I become a maintainer
First, all maintainers have 3 things
* They share responsibility in the project's success.
* They have made a long-term, recurring time investment to improve the project.
* They spend that time doing whatever needs to be done, not necessarily what
is the most interesting or fun.
Maintainers are often under-appreciated, because their work is harder to appreciate.
It's easy to appreciate a really cool and technically advanced feature. It's harder
to appreciate the absence of bugs, the slow but steady improvement in stability,
or the reliability of a release process. But those things distinguish a good
project from a great one.
Don't forget: being a maintainer is a time investment. Make sure you
will have time to make yourself available. You don't have to be a
maintainer to make a difference on the project!
If you want to become a meintainer, contact [xuri.me](https://xuri.me) and given a introduction of you.
## Community guidelines
We want to keep the community awesome, growing and collaborative. We need
your help to keep it that way. To help with this we've come up with some general
guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members:
no regional, racial, gender, or other abuse will be tolerated. We like
nice people way better than mean ones!
* Encourage diversity and participation: Make everyone in our community feel
welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break
the law.
* Stay on topic: Make sure that you are posting to the correct channel and
avoid off-topic discussions. Remember when you update an issue or respond
to an email you are potentially sending to a large number of people. Please
consider this before you update. Also remember that nobody likes spam.
* Don't send email to the maintainers: There's no need to send email to the
maintainers to ask them to investigate an issue or to take a look at a
pull request. Instead of sending an email, GitHub mentions should be
used to ping maintainers to review a pull request, a proposal or an
issue.
### Guideline violations — 3 strikes method
The point of this section is not to find opportunities to punish people, but we
do need a fair way to deal with people who are making our community suck.
1. First occurrence: We'll give you a friendly, but public reminder that the
behavior is inappropriate according to our guidelines.
2. Second occurrence: We will send you a private message with a warning that
any additional violations will result in removal from the community.
3. Third occurrence: Depending on the violation, we may need to delete or ban
your account.
**Notes:**
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
have spam all over the place.
* Violations are forgiven after 6 months of good behavior, and we won't hold a
grudge.
* People who commit minor infractions will get some education, rather than
hammering them in the 3 strikes process.
* The rules apply equally to everyone in the community, no matter how much
you've contributed.
* Extreme violations of a threatening, abusive, destructive or illegal nature
will be addressed immediately and are not subject to 3 strikes or forgiveness.
* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of
appeals, we know that mistakes happen, and we'll work with you to come up with a
fair solution if there has been a misunderstanding.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant its own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.
## Code Review Comments and Effective Go Guidelines
[CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
### Package Comment
Every package should have a package comment, a block comment preceding the package clause.
For multi-file packages, the package comment only needs to be present in one file, and any one will do.
The package comment should introduce the package and provide information relevant to the package as a
whole. It will appear first on the godoc page and should set up the detailed documentation that follows.
### Single Method Interface Name
By convention, one-method interfaces are named by the method name plus an -er suffix
or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
There are a number of such names and it's productive to honor them and the function names they capture.
Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion,
don't give your method one of those names unless it has the same signature and meaning. Conversely,
if your type implements a method with the same meaning as a method on a well-known type, give it the
same name and signature; call your string-converter method String not ToString.
### Avoid Annotations in Comments
Comments do not need extra formatting such as banners of stars. The generated output
may not even be presented in a fixed-width font, so don't depend on spacing for alignment—godoc,
like gofmt, takes care of that. The comments are uninterpreted plain text, so HTML and other
annotations such as _this_ will reproduce verbatim and should not be used. One adjustment godoc
does do is to display indented text in a fixed-width font, suitable for program snippets.
The package comment for the fmt package uses this to good effect.
### Comment First Word as Subject
Doc comments work best as complete sentences, which allow a wide variety of automated presentations.
The first sentence should be a one-sentence summary that starts with the name being declared.
### Good Package Name
It's helpful if everyone using the package can use the same name
to refer to its contents, which implies that the package name should
be good: short, concise, evocative. By convention, packages are
given lower case, single-word names; there should be no need for
underscores or mixedCaps. Err on the side of brevity, since everyone
using your package will be typing that name. And don't worry about
collisions a priori. The package name is only the default name for
imports; it need not be unique across all source code, and in the
rare case of a collision the importing package can choose a different
name to use locally. In any case, confusion is rare because the file
name in the import determines just which package is being used.
### Avoid Renaming Imports
Avoid renaming imports except to avoid a name collision; good package names
should not require renaming. In the event of collision, prefer to rename the
most local or project-specific import.
### Context as First Argument
Values of the context.Context type carry security credentials, tracing information,
deadlines, and cancellation signals across API and process boundaries. Go programs
pass Contexts explicitly along the entire function call chain from incoming RPCs
and HTTP requests to outgoing requests.
Most functions that use a Context should accept it as their first parameter.
### Do Not Discard Errors
Do not discard errors using _ variables. If a function returns an error,
check it to make sure the function succeeded. Handle the error, return it, or,
in truly exceptional situations, panic.
### Go Error Format
Error strings should not be capitalized (unless beginning with proper nouns
or acronyms) or end with punctuation, since they are usually printed following
other context. That is, use fmt.Errorf("something bad") not fmt.Errorf("Something bad"),
so that log.Printf("Reading %s: %v", filename, err) formats without a spurious
capital letter mid-message. This does not apply to logging, which is implicitly
line-oriented and not combined inside other messages.
### Use Crypto Rand
Do not use package math/rand to generate keys, even
throwaway ones. Unseeded, the generator is completely predictable.
Seeded with time.Nanoseconds(), there are just a few bits of entropy.
Instead, use crypto/rand's Reader, and if you need text, print to
hexadecimal or base64
@@ -0,0 +1,384 @@
<!-- use this template to generate the contributor docs with the following command: `$ lingo run docs --template CONTRIBUTING_TEMPLATE.md --output CONTRIBUTING.md` -->
# Contributing to excelize
Want to hack on excelize? Awesome! This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
you read our [community guidelines](#community-guidelines) before you
start participating.
## Topics
* [Reporting Security Issues](#reporting-security-issues)
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
* [Reporting Issues](#reporting-other-issues)
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
* [Community Guidelines](#community-guidelines)
## Reporting security issues
The excelize maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[xuri.me](https://xuri.me).
Security reports are greatly appreciated and we will publicly thank you for it.
We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [our issue database](https://github.com/360EntSecGroup-Skylar/excelize/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, you can use the "subscribe" button to get notified on
updates. Do *not* leave random "+1" or "I have this too" comments, as they
only clutter the discussion, and don't help resolving it. However, if you
have ways to reproduce the issue or have additional information that may help
resolving the issue, please leave a comment.
When reporting issues, always include the output of `go env`.
Also include the steps required to reproduce the problem if possible and
applicable. This information will help us review and fix your issue faster.
When sending lengthy log-files, consider posting them as a gist [https://gist.github.com](https://gist.github.com).
Don't forget to remove sensitive data from your logfiles before posting (you can
replace those parts with "REDACTED").
## Quick contribution tips and guidelines
This section gives the experienced contributor some tips and guidelines.
### Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it. Any significant improvement should be
documented as [a GitHub issue](https://github.com/360EntSecGroup-Skylar/excelize/issues) before
anybody starts working on it.
We are always thrilled to receive pull requests. We do our best to process them
quickly. If your pull request is not accepted on the first try,
don't get discouraged!
### Design and cleanup proposals
You can propose new designs for existing excelize features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project.
We try hard to keep excelize lean and focused. Excelize can't do everything for
everybody. This means that we might decide against incorporating a new feature.
However, there might be a way to implement that feature *on top of* excelize.
### Conventions
Fork the repository and make changes on your fork in a feature branch:
* If it's a bug fix branch, name it XXXX-something where XXXX is the number of
the issue.
* If it's a feature branch, create an enhancement issue to announce
your intentions, and name it XXXX-something where XXXX is the number of the
issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test on your branch before
submitting a pull request.
Update the documentation when creating or modifying features. Test your
documentation changes for clarity, concision, and correctness, as well as a
clean documentation build.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a reference
to all the issues that they address.
### Successful Changes
Before contributing large or high impact changes, make the effort to coordinate
with the maintainers of the project before submitting a pull request. This
prevents you from doing extra work that may or may not be merged.
Large PRs that are just submitted without any prior communication are unlikely
to be successful.
While pull requests are the methodology for submitting changes to code, changes
are much more likely to be accepted if they are accompanied by additional
engineering work. While we don't define this explicitly, most of these goals
are accomplished through communication of the design goals and subsequent
solutions. Often times, it helps to first state the problem before presenting
solutions.
Typically, the best methods of accomplishing this are to submit an issue,
stating the problem. This issue can include a problem statement and a
checklist with requirements. If solutions are proposed, alternatives should be
listed and eliminated. Even if the criteria for elimination of a solution is
frivolous, say so.
Larger changes typically work best with design documents. These are focused on
providing context to the design at the time the feature was conceived and can
inform future documentation contributions.
### Commit Messages
Commit messages must start with a capitalized and short summary
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Commit messages should follow best practices, including explaining the context
of the problem and how it was solved, including in caveats or follow up changes
required. They should tell the story of the change and provide readers
understanding of what led to it.
In practice, the best approach to maintaining a nice commit message is to
leverage a `git add -p` and `git commit --amend` to formulate a solid
changeset. This allows one to piece together a change, as information becomes
available.
If you squash a series of commits, don't just submit that. Re-write the commit
message, as if the series of commits was a single stroke of brilliance.
That said, there is no requirement to have a single commit for a PR, as long as
each commit tells the story. For example, if there is a feature that requires a
package, it might make sense to have the package in a separate commit then have
a subsequent commit that uses it.
Remember, you're telling part of the story with the commit message. Don't make
your chapter weird.
### Review
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
Pull requests must be cleanly rebased on top of master without multiple branches
mixed into the PR.
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
After every commit, make sure the test passes. Include documentation
changes in the same pull request so that a revert would remove all traces of
the feature or fix.
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
close an issue. Including references automatically closes the issue on a merge.
Please see the [Coding Style](#coding-style) for further guidelines.
### Merge approval
The excelize maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance.
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```text
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Ri Xu https://xuri.me
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
### How can I become a maintainer
First, all maintainers have 3 things
* They share responsibility in the project's success.
* They have made a long-term, recurring time investment to improve the project.
* They spend that time doing whatever needs to be done, not necessarily what
is the most interesting or fun.
Maintainers are often under-appreciated, because their work is harder to appreciate.
It's easy to appreciate a really cool and technically advanced feature. It's harder
to appreciate the absence of bugs, the slow but steady improvement in stability,
or the reliability of a release process. But those things distinguish a good
project from a great one.
Don't forget: being a maintainer is a time investment. Make sure you
will have time to make yourself available. You don't have to be a
maintainer to make a difference on the project!
If you want to become a meintainer, contact [xuri.me](https://xuri.me) and given a introduction of you.
## Community guidelines
We want to keep the community awesome, growing and collaborative. We need
your help to keep it that way. To help with this we've come up with some general
guidelines for the community as a whole:
* Be nice: Be courteous, respectful and polite to fellow community members:
no regional, racial, gender, or other abuse will be tolerated. We like
nice people way better than mean ones!
* Encourage diversity and participation: Make everyone in our community feel
welcome, regardless of their background and the extent of their
contributions, and do everything possible to encourage participation in
our community.
* Keep it legal: Basically, don't get us in trouble. Share only content that
you own, do not share private or sensitive information, and don't break
the law.
* Stay on topic: Make sure that you are posting to the correct channel and
avoid off-topic discussions. Remember when you update an issue or respond
to an email you are potentially sending to a large number of people. Please
consider this before you update. Also remember that nobody likes spam.
* Don't send email to the maintainers: There's no need to send email to the
maintainers to ask them to investigate an issue or to take a look at a
pull request. Instead of sending an email, GitHub mentions should be
used to ping maintainers to review a pull request, a proposal or an
issue.
### Guideline violations — 3 strikes method
The point of this section is not to find opportunities to punish people, but we
do need a fair way to deal with people who are making our community suck.
1. First occurrence: We'll give you a friendly, but public reminder that the
behavior is inappropriate according to our guidelines.
2. Second occurrence: We will send you a private message with a warning that
any additional violations will result in removal from the community.
3. Third occurrence: Depending on the violation, we may need to delete or ban
your account.
**Notes:**
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
have spam all over the place.
* Violations are forgiven after 6 months of good behavior, and we won't hold a
grudge.
* People who commit minor infractions will get some education, rather than
hammering them in the 3 strikes process.
* The rules apply equally to everyone in the community, no matter how much
you've contributed.
* Extreme violations of a threatening, abusive, destructive or illegal nature
will be addressed immediately and are not subject to 3 strikes or forgiveness.
* Contact [xuri.me](https://xuri.me) to report abuse or appeal violations. In the case of
appeals, we know that mistakes happen, and we'll work with you to come up with a
fair solution if there has been a misunderstanding.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant its own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
kool-aid is a lot easier than going thirsty.
## Code Review Comments and Effective Go Guidelines
[CodeLingo](https://codelingo.io) automatically checks every pull request against the following guidelines from [Effective Go](https://golang.org/doc/effective_go.html) and [Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
{{range .}}
### {{.title}}
{{.body}}
{{end}}
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2016 - 2019 360 Enterprise Security Group, Endpoint Security,
inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Excelize nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,45 @@
# PR Details
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
## How Has This Been Tested
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
+179
View File
@@ -0,0 +1,179 @@
<p align="center"><img width="650" src="./excelize.png" alt="Excelize logo"></p>
<p align="center">
<a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
<a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
<a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
<a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
<a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
</p>
# Excelize
## Introduction
Excelize is a library written in pure Go and providing a set of functions that allow you to write to and read from XLSX files. Support reads and writes XLSX file generated by Microsoft Excel&trade; 2007 and later. Support save file without losing original charts of XLSX. This library needs Go version 1.8 or later. The full API docs can be seen using go's built-in documentation tool, or online at [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) and [docs reference](https://xuri.me/excelize/).
## Basic Usage
### Installation
```go
go get github.com/360EntSecGroup-Skylar/excelize
```
### Create XLSX file
Here is a minimal example usage that will create XLSX file.
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx := excelize.NewFile()
// Create a new sheet.
index := xlsx.NewSheet("Sheet2")
// Set value of a cell.
xlsx.SetCellValue("Sheet2", "A2", "Hello world.")
xlsx.SetCellValue("Sheet1", "B2", 100)
// Set active sheet of the workbook.
xlsx.SetActiveSheet(index)
// Save xlsx file by the given path.
err := xlsx.SaveAs("./Book1.xlsx")
if err != nil {
fmt.Println(err)
}
}
```
### Reading XLSX file
The following constitutes the bare to read a XLSX document.
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx, err := excelize.OpenFile("./Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// Get value from cell by given worksheet name and axis.
cell := xlsx.GetCellValue("Sheet1", "B2")
fmt.Println(cell)
// Get all the rows in the Sheet1.
rows := xlsx.GetRows("Sheet1")
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
}
```
### Add chart to XLSX file
With Excelize chart generation and management is as easy as a few lines of code. You can build charts based off data in your worksheet or generate charts without any data in your worksheet at all.
<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
xlsx := excelize.NewFile()
for k, v := range categories {
xlsx.SetCellValue("Sheet1", k, v)
}
for k, v := range values {
xlsx.SetCellValue("Sheet1", k, v)
}
xlsx.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
// Save xlsx file by the given path.
err := xlsx.SaveAs("./Book1.xlsx")
if err != nil {
fmt.Println(err)
}
}
```
### Add picture to XLSX file
```go
package main
import (
"fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx, err := excelize.OpenFile("./Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// Insert a picture.
err = xlsx.AddPicture("Sheet1", "A2", "./image1.png", "")
if err != nil {
fmt.Println(err)
}
// Insert a picture to worksheet with scaling.
err = xlsx.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
if err != nil {
fmt.Println(err)
}
// Insert a picture offset in the cell with printing support.
err = xlsx.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
if err != nil {
fmt.Println(err)
}
// Save the xlsx file with the origin path.
err = xlsx.Save()
if err != nil {
fmt.Println(err)
}
}
```
## Contributing
Contributions are welcome! Open a pull request to fix a bug, or open an issue to discuss a new feature or change. XML is compliant with [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](http://www.ecma-international.org/publications/standards/Ecma-376.htm).
## Credits
Some struct of XML originally by [tealeg/xlsx](https://github.com/tealeg/xlsx).
## Licenses
This program is under the terms of the BSD 3-Clause License. See [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2F360EntSecGroup-Skylar%2Fexcelize.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2F360EntSecGroup-Skylar%2Fexcelize?ref=badge_large)
+179
View File
@@ -0,0 +1,179 @@
<p align="center"><img width="650" src="./excelize.png" alt="Excelize logo"></p>
<p align="center">
<a href="https://travis-ci.org/360EntSecGroup-Skylar/excelize"><img src="https://travis-ci.org/360EntSecGroup-Skylar/excelize.svg?branch=master" alt="Build Status"></a>
<a href="https://codecov.io/gh/360EntSecGroup-Skylar/excelize"><img src="https://codecov.io/gh/360EntSecGroup-Skylar/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
<a href="https://goreportcard.com/report/github.com/360EntSecGroup-Skylar/excelize"><img src="https://goreportcard.com/badge/github.com/360EntSecGroup-Skylar/excelize" alt="Go Report Card"></a>
<a href="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize"><img src="https://godoc.org/github.com/360EntSecGroup-Skylar/excelize?status.svg" alt="GoDoc"></a>
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
<a href="https://www.paypal.me/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
</p>
# Excelize
## 简介
Excelize 是 Go 语言编写的用于操作 Office Excel 文档类库,基于 ECMA-376 Office OpenXML 标准。可以使用它来读取、写入由 Microsoft Excel&trade; 2007 及以上版本创建的 XLSX 文档。相比较其他的开源类库,Excelize 支持写入原本带有图片(表)、透视表和切片器等复杂样式的文档,还支持向 Excel 文档中插入图片与图表,并且在保存后不会丢失文档原有样式,可以应用于各类报表系统中。使用本类库要求使用的 Go 语言为 1.8 或更高版本,完整的 API 使用文档请访问 [godoc.org](https://godoc.org/github.com/360EntSecGroup-Skylar/excelize) 或查看 [参考文档](https://xuri.me/excelize/)。
## 快速上手
### 安装
```go
go get github.com/360EntSecGroup-Skylar/excelize
```
### 创建 Excel 文档
下面是一个创建 Excel 文档的简单例子:
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx := excelize.NewFile()
// 创建一个工作表
index := xlsx.NewSheet("Sheet2")
// 设置单元格的值
xlsx.SetCellValue("Sheet2", "A2", "Hello world.")
xlsx.SetCellValue("Sheet1", "B2", 100)
// 设置工作簿的默认工作表
xlsx.SetActiveSheet(index)
// 根据指定路径保存文件
err := xlsx.SaveAs("./Book1.xlsx")
if err != nil {
fmt.Println(err)
}
}
```
### 读取 Excel 文档
下面是读取 Excel 文档的例子:
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx, err := excelize.OpenFile("./Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// 获取工作表中指定单元格的值
cell := xlsx.GetCellValue("Sheet1", "B2")
fmt.Println(cell)
// 获取 Sheet1 上所有单元格
rows := xlsx.GetRows("Sheet1")
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
}
```
### 在 Excel 文档中创建图表
使用 Excelize 生成图表十分简单,仅需几行代码。您可以根据工作表中的已有数据构建图表,或向工作表中添加数据并创建图表。
<p align="center"><img width="650" src="./test/images/chart.png" alt="Excelize"></p>
```go
package main
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
xlsx := excelize.NewFile()
for k, v := range categories {
xlsx.SetCellValue("Sheet1", k, v)
}
for k, v := range values {
xlsx.SetCellValue("Sheet1", k, v)
}
xlsx.AddChart("Sheet1", "E1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)
// 根据指定路径保存文件
err := xlsx.SaveAs("./Book1.xlsx")
if err != nil {
fmt.Println(err)
}
}
```
### 向 Excel 文档中插入图片
```go
package main
import (
"fmt"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"github.com/360EntSecGroup-Skylar/excelize"
)
func main() {
xlsx, err := excelize.OpenFile("./Book1.xlsx")
if err != nil {
fmt.Println(err)
return
}
// 插入图片
err = xlsx.AddPicture("Sheet1", "A2", "./image1.png", "")
if err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的缩放比例
err = xlsx.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`)
if err != nil {
fmt.Println(err)
}
// 在工作表中插入图片,并设置图片的打印属性
err = xlsx.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`)
if err != nil {
fmt.Println(err)
}
// 保存文件
err = xlsx.Save()
if err != nil {
fmt.Println(err)
}
}
```
## 社区合作
欢迎您为此项目贡献代码,提出建议或问题、修复 Bug 以及参与讨论对新功能的想法。 XML 符合标准: [part 1 of the 5th edition of the ECMA-376 Standard for Office Open XML](http://www.ecma-international.org/publications/standards/Ecma-376.htm)。
## 致谢
本类库中部分 XML 结构体的定义参考了开源项目:[tealeg/xlsx](https://github.com/tealeg/xlsx).
## 开源许可
本项目遵循 BSD 3-Clause 开源许可协议,访问 [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) 查看许可协议文件。
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2F360EntSecGroup-Skylar%2Fexcelize.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2F360EntSecGroup-Skylar%2Fexcelize?ref=badge_large)
+599
View File
@@ -0,0 +1,599 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
package excelize
import (
"encoding/xml"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
const (
// STCellFormulaTypeArray defined the formula is an array formula.
STCellFormulaTypeArray = "array"
// STCellFormulaTypeDataTable defined the formula is a data table formula.
STCellFormulaTypeDataTable = "dataTable"
// STCellFormulaTypeNormal defined the formula is a regular cell formula.
STCellFormulaTypeNormal = "normal"
// STCellFormulaTypeShared defined the formula is part of a shared formula.
STCellFormulaTypeShared = "shared"
)
// mergeCellsParser provides a function to check merged cells in worksheet by
// given axis.
func (f *File) mergeCellsParser(xlsx *xlsxWorksheet, axis string) string {
axis = strings.ToUpper(axis)
if xlsx.MergeCells != nil {
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
if checkCellInArea(axis, xlsx.MergeCells.Cells[i].Ref) {
axis = strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0]
}
}
}
return axis
}
// SetCellValue provides a function to set value of a cell. The following
// shows the supported data types:
//
// int
// int8
// int16
// int32
// int64
// uint
// uint8
// uint16
// uint32
// uint64
// float32
// float64
// string
// []byte
// time.Duration
// time.Time
// bool
// nil
//
// Note that default date format is m/d/yy h:mm of time.Time type value. You can
// set numbers format by SetCellStyle() method.
func (f *File) SetCellValue(sheet, axis string, value interface{}) {
switch t := value.(type) {
case float32:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float32)), 'f', -1, 32))
case float64:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(float64)), 'f', -1, 64))
case string:
f.SetCellStr(sheet, axis, t)
case []byte:
f.SetCellStr(sheet, axis, string(t))
case time.Duration:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(value.(time.Duration).Seconds()/86400), 'f', -1, 32))
f.setDefaultTimeStyle(sheet, axis, 21)
case time.Time:
f.SetCellDefault(sheet, axis, strconv.FormatFloat(float64(timeToExcelTime(timeToUTCTime(value.(time.Time)))), 'f', -1, 64))
f.setDefaultTimeStyle(sheet, axis, 22)
case nil:
f.SetCellStr(sheet, axis, "")
case bool:
f.SetCellBool(sheet, axis, bool(value.(bool)))
default:
f.setCellIntValue(sheet, axis, value)
}
}
// setCellIntValue provides a function to set int value of a cell.
func (f *File) setCellIntValue(sheet, axis string, value interface{}) {
switch value.(type) {
case int:
f.SetCellInt(sheet, axis, value.(int))
case int8:
f.SetCellInt(sheet, axis, int(value.(int8)))
case int16:
f.SetCellInt(sheet, axis, int(value.(int16)))
case int32:
f.SetCellInt(sheet, axis, int(value.(int32)))
case int64:
f.SetCellInt(sheet, axis, int(value.(int64)))
case uint:
f.SetCellInt(sheet, axis, int(value.(uint)))
case uint8:
f.SetCellInt(sheet, axis, int(value.(uint8)))
case uint16:
f.SetCellInt(sheet, axis, int(value.(uint16)))
case uint32:
f.SetCellInt(sheet, axis, int(value.(uint32)))
case uint64:
f.SetCellInt(sheet, axis, int(value.(uint64)))
default:
f.SetCellStr(sheet, axis, fmt.Sprintf("%v", value))
}
}
// SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell coordinates and cell value.
func (f *File) SetCellBool(sheet, axis string, value bool) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
xlsx.SheetData.Row[xAxis].C[yAxis].T = "b"
if value {
xlsx.SheetData.Row[xAxis].C[yAxis].V = "1"
} else {
xlsx.SheetData.Row[xAxis].C[yAxis].V = "0"
}
}
// GetCellValue provides a function to get formatted value from cell by given
// worksheet name and axis in XLSX file. If it is possible to apply a format
// to the cell value, it will do so, if not then an error will be returned,
// along with the raw value of the cell.
func (f *File) GetCellValue(sheet, axis string) string {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return ""
}
xAxis := row - 1
rows := len(xlsx.SheetData.Row)
if rows > 1 {
lastRow := xlsx.SheetData.Row[rows-1].R
if lastRow >= rows {
rows = lastRow
}
}
if rows < xAxis {
return ""
}
for k := range xlsx.SheetData.Row {
if xlsx.SheetData.Row[k].R == row {
for i := range xlsx.SheetData.Row[k].C {
if axis == xlsx.SheetData.Row[k].C[i].R {
val, _ := xlsx.SheetData.Row[k].C[i].getValueFrom(f, f.sharedStringsReader())
return val
}
}
}
}
return ""
}
// formattedValue provides a function to returns a value after formatted. If
// it is possible to apply a format to the cell value, it will do so, if not
// then an error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(s int, v string) string {
if s == 0 {
return v
}
styleSheet := f.stylesReader()
ok := builtInNumFmtFunc[styleSheet.CellXfs.Xf[s].NumFmtID]
if ok != nil {
return ok(styleSheet.CellXfs.Xf[s].NumFmtID, v)
}
return v
}
// GetCellStyle provides a function to get cell style index by given worksheet
// name and cell coordinates.
func (f *File) GetCellStyle(sheet, axis string) int {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return 0
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
return f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
}
// GetCellFormula provides a function to get formula from cell by given
// worksheet name and axis in XLSX file.
func (f *File) GetCellFormula(sheet, axis string) string {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return ""
}
xAxis := row - 1
rows := len(xlsx.SheetData.Row)
if rows > 1 {
lastRow := xlsx.SheetData.Row[rows-1].R
if lastRow >= rows {
rows = lastRow
}
}
if rows < xAxis {
return ""
}
for k := range xlsx.SheetData.Row {
if xlsx.SheetData.Row[k].R == row {
for i := range xlsx.SheetData.Row[k].C {
if axis == xlsx.SheetData.Row[k].C[i].R {
if xlsx.SheetData.Row[k].C[i].F == nil {
continue
}
if xlsx.SheetData.Row[k].C[i].F.T == STCellFormulaTypeShared {
return getSharedForumula(xlsx, xlsx.SheetData.Row[k].C[i].F.Si)
}
return xlsx.SheetData.Row[k].C[i].F.Content
}
}
}
}
return ""
}
// getSharedForumula find a cell contains the same formula as another cell,
// the "shared" value can be used for the t attribute and the si attribute can
// be used to refer to the cell containing the formula. Two formulas are
// considered to be the same when their respective representations in
// R1C1-reference notation, are the same.
//
// Note that this function not validate ref tag to check the cell if or not in
// allow area, and always return origin shared formula.
func getSharedForumula(xlsx *xlsxWorksheet, si string) string {
for k := range xlsx.SheetData.Row {
for i := range xlsx.SheetData.Row[k].C {
if xlsx.SheetData.Row[k].C[i].F == nil {
continue
}
if xlsx.SheetData.Row[k].C[i].F.T != STCellFormulaTypeShared {
continue
}
if xlsx.SheetData.Row[k].C[i].F.Si != si {
continue
}
if xlsx.SheetData.Row[k].C[i].F.Ref != "" {
return xlsx.SheetData.Row[k].C[i].F.Content
}
}
}
return ""
}
// SetCellFormula provides a function to set cell formula by given string and
// worksheet name.
func (f *File) SetCellFormula(sheet, axis, formula string) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
if xlsx.SheetData.Row[xAxis].C[yAxis].F != nil {
xlsx.SheetData.Row[xAxis].C[yAxis].F.Content = formula
} else {
f := xlsxF{
Content: formula,
}
xlsx.SheetData.Row[xAxis].C[yAxis].F = &f
}
}
// SetCellHyperLink provides a function to set cell hyperlink by given
// worksheet name and link URL address. LinkType defines two types of
// hyperlink "External" for web site or "Location" for moving to one of cell
// in this workbook. The below is example for external link.
//
// xlsx.SetCellHyperLink("Sheet1", "A3", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
// // Set underline and font color style for the cell.
// style, _ := xlsx.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`)
// xlsx.SetCellStyle("Sheet1", "A3", "A3", style)
//
// A this is another example for "Location":
//
// xlsx.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location")
//
func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
linkTypes := map[string]xlsxHyperlink{
"External": {},
"Location": {Location: link},
}
hyperlink, ok := linkTypes[linkType]
if !ok || axis == "" {
return
}
hyperlink.Ref = axis
if linkType == "External" {
rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType)
hyperlink.RID = "rId" + strconv.Itoa(rID)
}
if xlsx.Hyperlinks == nil {
xlsx.Hyperlinks = &xlsxHyperlinks{}
}
xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink, hyperlink)
}
// GetCellHyperLink provides a function to get cell hyperlink by given
// worksheet name and axis. Boolean type value link will be ture if the cell
// has a hyperlink and the target is the address of the hyperlink. Otherwise,
// the value of link will be false and the value of the target will be a blank
// string. For example get hyperlink of Sheet1!H6:
//
// link, target := xlsx.GetCellHyperLink("Sheet1", "H6")
//
func (f *File) GetCellHyperLink(sheet, axis string) (bool, string) {
var link bool
var target string
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
if xlsx.Hyperlinks == nil || axis == "" {
return link, target
}
for h := range xlsx.Hyperlinks.Hyperlink {
if xlsx.Hyperlinks.Hyperlink[h].Ref == axis {
link = true
target = xlsx.Hyperlinks.Hyperlink[h].Location
if xlsx.Hyperlinks.Hyperlink[h].RID != "" {
target = f.getSheetRelationshipsTargetByID(sheet, xlsx.Hyperlinks.Hyperlink[h].RID)
}
}
}
return link, target
}
// MergeCell provides a function to merge cells by given coordinate area and
// sheet name. For example create a merged cell of D3:E9 on Sheet1:
//
// xlsx.MergeCell("Sheet1", "D3", "E9")
//
// If you create a merged cell that overlaps with another existing merged cell,
// those merged cells that already exist will be removed.
func (f *File) MergeCell(sheet, hcell, vcell string) {
if hcell == vcell {
return
}
hcell = strings.ToUpper(hcell)
vcell = strings.ToUpper(vcell)
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
hcol := string(strings.Map(letterOnlyMapF, hcell))
hrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, hcell))
hyAxis := hrow - 1
hxAxis := TitleToNumber(hcol)
vcol := string(strings.Map(letterOnlyMapF, vcell))
vrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, vcell))
vyAxis := vrow - 1
vxAxis := TitleToNumber(vcol)
if vxAxis < hxAxis {
hcell, vcell = vcell, hcell
vxAxis, hxAxis = hxAxis, vxAxis
}
if vyAxis < hyAxis {
hcell, vcell = vcell, hcell
vyAxis, hyAxis = hyAxis, vyAxis
}
xlsx := f.workSheetReader(sheet)
if xlsx.MergeCells != nil {
mergeCell := xlsxMergeCell{}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
// Delete the merged cells of the overlapping area.
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
if checkCellInArea(hcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[0], mergeCell.Ref) {
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
} else if checkCellInArea(vcell, xlsx.MergeCells.Cells[i].Ref) || checkCellInArea(strings.Split(xlsx.MergeCells.Cells[i].Ref, ":")[1], mergeCell.Ref) {
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:i], xlsx.MergeCells.Cells[i+1:]...)
}
}
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells, &mergeCell)
} else {
mergeCell := xlsxMergeCell{}
// Correct the coordinate area, such correct C1:B3 to B1:C3.
mergeCell.Ref = ToAlphaString(hxAxis) + strconv.Itoa(hyAxis+1) + ":" + ToAlphaString(vxAxis) + strconv.Itoa(vyAxis+1)
mergeCells := xlsxMergeCells{}
mergeCells.Cells = append(mergeCells.Cells, &mergeCell)
xlsx.MergeCells = &mergeCells
}
}
// SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell coordinates and cell value.
func (f *File) SetCellInt(sheet, axis string, value int) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
xlsx.SheetData.Row[xAxis].C[yAxis].V = strconv.Itoa(value)
}
// prepareCellStyle provides a function to prepare style index of cell in
// worksheet by given column index and style index.
func (f *File) prepareCellStyle(xlsx *xlsxWorksheet, col, style int) int {
if xlsx.Cols != nil && style == 0 {
for _, v := range xlsx.Cols.Col {
if v.Min <= col && col <= v.Max {
style = v.Style
}
}
}
return style
}
// SetCellStr provides a function to set string type value of a cell. Total
// number of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet, axis, value string) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
if len(value) > 32767 {
value = value[0:32767]
}
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
// Leading space(s) character detection.
if len(value) > 0 {
if value[0] == 32 {
xlsx.SheetData.Row[xAxis].C[yAxis].XMLSpace = xml.Attr{
Name: xml.Name{Space: NameSpaceXML, Local: "space"},
Value: "preserve",
}
}
}
xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
xlsx.SheetData.Row[xAxis].C[yAxis].T = "str"
xlsx.SheetData.Row[xAxis].C[yAxis].V = value
}
// SetCellDefault provides a function to set string type value of a cell as
// default format without escaping the cell.
func (f *File) SetCellDefault(sheet, axis, value string) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
xlsx.SheetData.Row[xAxis].C[yAxis].S = f.prepareCellStyle(xlsx, cell, xlsx.SheetData.Row[xAxis].C[yAxis].S)
xlsx.SheetData.Row[xAxis].C[yAxis].T = ""
xlsx.SheetData.Row[xAxis].C[yAxis].V = value
}
// SetSheetRow writes an array to row by given worksheet name, starting
// coordinate and a pointer to array type 'slice'. For example, writes an
// array to row 6 start with the cell B6 on Sheet1:
//
// xlsx.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2})
//
func (f *File) SetSheetRow(sheet, axis string, slice interface{}) {
xlsx := f.workSheetReader(sheet)
axis = f.mergeCellsParser(xlsx, axis)
col := string(strings.Map(letterOnlyMapF, axis))
row, err := strconv.Atoi(strings.Map(intOnlyMapF, axis))
if err != nil {
return
}
// Make sure 'slice' is a Ptr to Slice
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Ptr {
return
}
v = v.Elem()
if v.Kind() != reflect.Slice {
return
}
xAxis := row - 1
yAxis := TitleToNumber(col)
rows := xAxis + 1
cell := yAxis + 1
completeRow(xlsx, rows, cell)
completeCol(xlsx, rows, cell)
idx := 0
for i := cell - 1; i < v.Len()+cell-1; i++ {
c := ToAlphaString(i) + strconv.Itoa(row)
f.SetCellValue(sheet, c, v.Index(idx).Interface())
idx++
}
}
// checkCellInArea provides a function to determine if a given coordinate is
// within an area.
func checkCellInArea(cell, area string) bool {
cell = strings.ToUpper(cell)
area = strings.ToUpper(area)
ref := strings.Split(area, ":")
if len(ref) < 2 {
return false
}
from := ref[0]
to := ref[1]
col, row := getCellColRow(cell)
fromCol, fromRow := getCellColRow(from)
toCol, toRow := getCellColRow(to)
return axisLowerOrEqualThan(fromCol, col) && axisLowerOrEqualThan(col, toCol) && axisLowerOrEqualThan(fromRow, row) && axisLowerOrEqualThan(row, toRow)
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
tenets:
- import: codelingo/effective-go
- import: codelingo/code-review-comments
+376
View File
@@ -0,0 +1,376 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
package excelize
import (
"bytes"
"math"
"strconv"
"strings"
)
// Define the default cell size and EMU unit of measurement.
const (
defaultColWidthPixels float64 = 64
defaultRowHeightPixels float64 = 20
EMU int = 9525
)
// GetColVisible provides a function to get visible of a single column by given
// worksheet name and column name. For example, get visible state of column D
// in Sheet1:
//
// xlsx.GetColVisible("Sheet1", "D")
//
func (f *File) GetColVisible(sheet, column string) bool {
xlsx := f.workSheetReader(sheet)
col := TitleToNumber(strings.ToUpper(column)) + 1
visible := true
if xlsx.Cols == nil {
return visible
}
for c := range xlsx.Cols.Col {
if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
visible = !xlsx.Cols.Col[c].Hidden
}
}
return visible
}
// SetColVisible provides a function to set visible of a single column by given
// worksheet name and column name. For example, hide column D in Sheet1:
//
// xlsx.SetColVisible("Sheet1", "D", false)
//
func (f *File) SetColVisible(sheet, column string, visible bool) {
xlsx := f.workSheetReader(sheet)
c := TitleToNumber(strings.ToUpper(column)) + 1
col := xlsxCol{
Min: c,
Max: c,
Hidden: !visible,
CustomWidth: true,
}
if xlsx.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, col)
xlsx.Cols = &cols
return
}
for v := range xlsx.Cols.Col {
if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
col = xlsx.Cols.Col[v]
}
}
col.Min = c
col.Max = c
col.Hidden = !visible
col.CustomWidth = true
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
}
// GetColOutlineLevel provides a function to get outline level of a single
// column by given worksheet name and column name. For example, get outline
// level of column D in Sheet1:
//
// xlsx.GetColOutlineLevel("Sheet1", "D")
//
func (f *File) GetColOutlineLevel(sheet, column string) uint8 {
xlsx := f.workSheetReader(sheet)
col := TitleToNumber(strings.ToUpper(column)) + 1
level := uint8(0)
if xlsx.Cols == nil {
return level
}
for c := range xlsx.Cols.Col {
if xlsx.Cols.Col[c].Min <= col && col <= xlsx.Cols.Col[c].Max {
level = xlsx.Cols.Col[c].OutlineLevel
}
}
return level
}
// SetColOutlineLevel provides a function to set outline level of a single
// column by given worksheet name and column name. For example, set outline
// level of column D in Sheet1 to 2:
//
// xlsx.SetColOutlineLevel("Sheet1", "D", 2)
//
func (f *File) SetColOutlineLevel(sheet, column string, level uint8) {
xlsx := f.workSheetReader(sheet)
c := TitleToNumber(strings.ToUpper(column)) + 1
col := xlsxCol{
Min: c,
Max: c,
OutlineLevel: level,
CustomWidth: true,
}
if xlsx.Cols == nil {
cols := xlsxCols{}
cols.Col = append(cols.Col, col)
xlsx.Cols = &cols
return
}
for v := range xlsx.Cols.Col {
if xlsx.Cols.Col[v].Min <= c && c <= xlsx.Cols.Col[v].Max {
col = xlsx.Cols.Col[v]
}
}
col.Min = c
col.Max = c
col.OutlineLevel = level
col.CustomWidth = true
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
}
// SetColWidth provides a function to set the width of a single column or
// multiple columns. For example:
//
// xlsx := excelize.NewFile()
// xlsx.SetColWidth("Sheet1", "A", "H", 20)
// err := xlsx.Save()
// if err != nil {
// fmt.Println(err)
// }
//
func (f *File) SetColWidth(sheet, startcol, endcol string, width float64) {
min := TitleToNumber(strings.ToUpper(startcol)) + 1
max := TitleToNumber(strings.ToUpper(endcol)) + 1
if min > max {
min, max = max, min
}
xlsx := f.workSheetReader(sheet)
col := xlsxCol{
Min: min,
Max: max,
Width: width,
CustomWidth: true,
}
if xlsx.Cols != nil {
xlsx.Cols.Col = append(xlsx.Cols.Col, col)
} else {
cols := xlsxCols{}
cols.Col = append(cols.Col, col)
xlsx.Cols = &cols
}
}
// positionObjectPixels calculate the vertices that define the position of a
// graphical object within the worksheet in pixels.
//
// +------------+------------+
// | A | B |
// +-----+------------+------------+
// | |(x1,y1) | |
// | 1 |(A1)._______|______ |
// | | | | |
// | | | | |
// +-----+----| OBJECT |-----+
// | | | | |
// | 2 | |______________. |
// | | | (B2)|
// | | | (x2,y2)|
// +-----+------------+------------+
//
// Example of an object that covers some of the area from cell A1 to B2.
//
// Based on the width and height of the object we need to calculate 8 vars:
//
// colStart, rowStart, colEnd, rowEnd, x1, y1, x2, y2.
//
// We also calculate the absolute x and y position of the top left vertex of
// the object. This is required for images.
//
// The width and height of the cells that the object occupies can be
// variable and have to be taken into account.
//
// The values of col_start and row_start are passed in from the calling
// function. The values of col_end and row_end are calculated by
// subtracting the width and height of the object from the width and
// height of the underlying cells.
//
// colStart # Col containing upper left corner of object.
// x1 # Distance to left side of object.
//
// rowStart # Row containing top left corner of object.
// y1 # Distance to top of object.
//
// colEnd # Col containing lower right corner of object.
// x2 # Distance to right side of object.
//
// rowEnd # Row containing bottom right corner of object.
// y2 # Distance to bottom of object.
//
// width # Width of object frame.
// height # Height of object frame.
//
// xAbs # Absolute distance to left side of object.
// yAbs # Absolute distance to top side of object.
//
func (f *File) positionObjectPixels(sheet string, colStart, rowStart, x1, y1, width, height int) (int, int, int, int, int, int, int, int) {
xAbs := 0
yAbs := 0
// Calculate the absolute x offset of the top-left vertex.
for colID := 1; colID <= colStart; colID++ {
xAbs += f.getColWidth(sheet, colID)
}
xAbs += x1
// Calculate the absolute y offset of the top-left vertex.
// Store the column change to allow optimisations.
for rowID := 1; rowID <= rowStart; rowID++ {
yAbs += f.getRowHeight(sheet, rowID)
}
yAbs += y1
// Adjust start column for offsets that are greater than the col width.
for x1 >= f.getColWidth(sheet, colStart) {
x1 -= f.getColWidth(sheet, colStart)
colStart++
}
// Adjust start row for offsets that are greater than the row height.
for y1 >= f.getRowHeight(sheet, rowStart) {
y1 -= f.getRowHeight(sheet, rowStart)
rowStart++
}
// Initialise end cell to the same as the start cell.
colEnd := colStart
rowEnd := rowStart
width += x1
height += y1
// Subtract the underlying cell widths to find end cell of the object.
for width >= f.getColWidth(sheet, colEnd) {
colEnd++
width -= f.getColWidth(sheet, colEnd)
}
// Subtract the underlying cell heights to find end cell of the object.
for height >= f.getRowHeight(sheet, rowEnd) {
rowEnd++
height -= f.getRowHeight(sheet, rowEnd)
}
// The end vertices are whatever is left from the width and height.
x2 := width
y2 := height
return colStart, rowStart, xAbs, yAbs, colEnd, rowEnd, x2, y2
}
// getColWidth provides a function to get column width in pixels by given
// sheet name and column index.
func (f *File) getColWidth(sheet string, col int) int {
xlsx := f.workSheetReader(sheet)
if xlsx.Cols != nil {
var width float64
for _, v := range xlsx.Cols.Col {
if v.Min <= col && col <= v.Max {
width = v.Width
}
}
if width != 0 {
return int(convertColWidthToPixels(width))
}
}
// Optimisation for when the column widths haven't changed.
return int(defaultColWidthPixels)
}
// GetColWidth provides a function to get column width by given worksheet name
// and column index.
func (f *File) GetColWidth(sheet, column string) float64 {
col := TitleToNumber(strings.ToUpper(column)) + 1
xlsx := f.workSheetReader(sheet)
if xlsx.Cols != nil {
var width float64
for _, v := range xlsx.Cols.Col {
if v.Min <= col && col <= v.Max {
width = v.Width
}
}
if width != 0 {
return width
}
}
// Optimisation for when the column widths haven't changed.
return defaultColWidthPixels
}
// InsertCol provides a function to insert a new column before given column
// index. For example, create a new column before column C in Sheet1:
//
// xlsx.InsertCol("Sheet1", "C")
//
func (f *File) InsertCol(sheet, column string) {
col := TitleToNumber(strings.ToUpper(column))
f.adjustHelper(sheet, col, -1, 1)
}
// RemoveCol provides a function to remove single column by given worksheet
// name and column index. For example, remove column C in Sheet1:
//
// xlsx.RemoveCol("Sheet1", "C")
//
func (f *File) RemoveCol(sheet, column string) {
xlsx := f.workSheetReader(sheet)
for r := range xlsx.SheetData.Row {
for k, v := range xlsx.SheetData.Row[r].C {
axis := v.R
col := string(strings.Map(letterOnlyMapF, axis))
if col == column {
xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C[:k], xlsx.SheetData.Row[r].C[k+1:]...)
}
}
}
col := TitleToNumber(strings.ToUpper(column))
f.adjustHelper(sheet, col, -1, -1)
}
// completeCol provieds function to completion column element tags of XML in a
// sheet.
func completeCol(xlsx *xlsxWorksheet, row, cell int) {
buffer := bytes.Buffer{}
for r := range xlsx.SheetData.Row {
if len(xlsx.SheetData.Row[r].C) < cell {
start := len(xlsx.SheetData.Row[r].C)
for iii := start; iii < cell; iii++ {
buffer.WriteString(ToAlphaString(iii))
buffer.WriteString(strconv.Itoa(r + 1))
xlsx.SheetData.Row[r].C = append(xlsx.SheetData.Row[r].C, xlsxC{
R: buffer.String(),
})
buffer.Reset()
}
}
}
}
// convertColWidthToPixels provieds function to convert the width of a cell
// from user's units to pixels. Excel rounds the column width to the nearest
// pixel. If the width hasn't been set by the user we use the default value.
// If the column is hidden it has a value of zero.
func convertColWidthToPixels(width float64) float64 {
var padding float64 = 5
var pixels float64
var maxDigitWidth float64 = 7
if width == 0 {
return pixels
}
if width < 1 {
pixels = (width * 12) + 0.5
return math.Ceil(pixels)
}
pixels = (width*maxDigitWidth + 0.5) + padding
return math.Ceil(pixels)
}
+273
View File
@@ -0,0 +1,273 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
package excelize
import (
"encoding/json"
"encoding/xml"
"fmt"
"strconv"
"strings"
)
// parseFormatCommentsSet provides a function to parse the format settings of
// the comment with default value.
func parseFormatCommentsSet(formatSet string) (*formatComment, error) {
format := formatComment{
Author: "Author:",
Text: " ",
}
err := json.Unmarshal([]byte(formatSet), &format)
return &format, err
}
// GetComments retrieves all comments and returns a map of worksheet name to
// the worksheet comments.
func (f *File) GetComments() (comments map[string][]Comment) {
comments = map[string][]Comment{}
for n := range f.sheetMap {
commentID := f.GetSheetIndex(n)
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
c, ok := f.XLSX[commentsXML]
if ok {
d := xlsxComments{}
xml.Unmarshal([]byte(c), &d)
sheetComments := []Comment{}
for _, comment := range d.CommentList.Comment {
sheetComment := Comment{}
if comment.AuthorID < len(d.Authors) {
sheetComment.Author = d.Authors[comment.AuthorID].Author
}
sheetComment.Ref = comment.Ref
sheetComment.AuthorID = comment.AuthorID
for _, text := range comment.Text.R {
sheetComment.Text += text.T
}
sheetComments = append(sheetComments, sheetComment)
}
comments[n] = sheetComments
}
}
return
}
// AddComment provides the method to add comment in a sheet by given worksheet
// index, cell and format set (such as author and text). Note that the max
// author length is 255 and the max text length is 32512. For example, add a
// comment in Sheet1!$A$30:
//
// xlsx.AddComment("Sheet1", "A30", `{"author":"Excelize: ","text":"This is a comment."}`)
//
func (f *File) AddComment(sheet, cell, format string) error {
formatSet, err := parseFormatCommentsSet(format)
if err != nil {
return err
}
// Read sheet data.
xlsx := f.workSheetReader(sheet)
commentID := f.countComments() + 1
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
sheetRelationshipsComments := "../comments" + strconv.Itoa(commentID) + ".xml"
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(commentID) + ".vml"
if xlsx.LegacyDrawing != nil {
// The worksheet already has a comments relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(sheet, xlsx.LegacyDrawing.RID)
commentID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1)
} else {
// Add first comment for given sheet.
rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "")
f.addSheetLegacyDrawing(sheet, rID)
}
commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml"
f.addComment(commentsXML, cell, formatSet)
var colCount int
for i, l := range strings.Split(formatSet.Text, "\n") {
if ll := len(l); ll > colCount {
if i == 0 {
ll += len(formatSet.Author)
}
colCount = ll
}
}
f.addDrawingVML(commentID, drawingVML, cell, strings.Count(formatSet.Text, "\n")+1, colCount)
f.addContentTypePart(commentID, "comments")
return err
}
// addDrawingVML provides a function to create comment as
// xl/drawings/vmlDrawing%d.vml by given commit ID and cell.
func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount, colCount int) {
col := string(strings.Map(letterOnlyMapF, cell))
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell))
xAxis := row - 1
yAxis := TitleToNumber(col)
vml := vmlDrawing{
XMLNSv: "urn:schemas-microsoft-com:vml",
XMLNSo: "urn:schemas-microsoft-com:office:office",
XMLNSx: "urn:schemas-microsoft-com:office:excel",
XMLNSmv: "http://macVmlSchemaUri",
Shapelayout: &xlsxShapelayout{
Ext: "edit",
IDmap: &xlsxIDmap{
Ext: "edit",
Data: commentID,
},
},
Shapetype: &xlsxShapetype{
ID: "_x0000_t202",
Coordsize: "21600,21600",
Spt: 202,
Path: "m0,0l0,21600,21600,21600,21600,0xe",
Stroke: &xlsxStroke{
Joinstyle: "miter",
},
VPath: &vPath{
Gradientshapeok: "t",
Connecttype: "miter",
},
},
}
sp := encodeShape{
Fill: &vFill{
Color2: "#fbfe82",
Angle: -180,
Type: "gradient",
Fill: &oFill{
Ext: "view",
Type: "gradientUnscaled",
},
},
Shadow: &vShadow{
On: "t",
Color: "black",
Obscured: "t",
},
Path: &vPath{
Connecttype: "none",
},
Textbox: &vTextbox{
Style: "mso-direction-alt:auto",
Div: &xlsxDiv{
Style: "text-align:left",
},
},
ClientData: &xClientData{
ObjectType: "Note",
Anchor: fmt.Sprintf(
"%d, 23, %d, 0, %d, %d, %d, 5",
1+yAxis, 1+xAxis, 2+yAxis+lineCount, colCount+yAxis, 2+xAxis+lineCount),
AutoFill: "True",
Row: xAxis,
Column: yAxis,
},
}
s, _ := xml.Marshal(sp)
shape := xlsxShape{
ID: "_x0000_s1025",
Type: "#_x0000_t202",
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
Fillcolor: "#fbf6d6",
Strokecolor: "#edeaa1",
Val: string(s[13 : len(s)-14]),
}
c, ok := f.XLSX[drawingVML]
if ok {
d := decodeVmlDrawing{}
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
for _, v := range d.Shape {
s := xlsxShape{
ID: "_x0000_s1025",
Type: "#_x0000_t202",
Style: "position:absolute;73.5pt;width:108pt;height:59.25pt;z-index:1;visibility:hidden",
Fillcolor: "#fbf6d6",
Strokecolor: "#edeaa1",
Val: v.Val,
}
vml.Shape = append(vml.Shape, s)
}
}
vml.Shape = append(vml.Shape, shape)
v, _ := xml.Marshal(vml)
f.XLSX[drawingVML] = v
}
// addComment provides a function to create chart as xl/comments%d.xml by
// given cell and format sets.
func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
a := formatSet.Author
t := formatSet.Text
if len(a) > 255 {
a = a[0:255]
}
if len(t) > 32512 {
t = t[0:32512]
}
comments := xlsxComments{
Authors: []xlsxAuthor{
{
Author: formatSet.Author,
},
},
}
cmt := xlsxComment{
Ref: cell,
AuthorID: 0,
Text: xlsxText{
R: []xlsxR{
{
RPr: &xlsxRPr{
B: " ",
Sz: &attrValFloat{Val: 9},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: "Calibri"},
Family: &attrValInt{Val: 2},
},
T: a,
},
{
RPr: &xlsxRPr{
Sz: &attrValFloat{Val: 9},
Color: &xlsxColor{
Indexed: 81,
},
RFont: &attrValString{Val: "Calibri"},
Family: &attrValInt{Val: 2},
},
T: t,
},
},
},
}
c, ok := f.XLSX[commentsXML]
if ok {
d := xlsxComments{}
_ = xml.Unmarshal(namespaceStrictToTransitional(c), &d)
comments.CommentList.Comment = append(comments.CommentList.Comment, d.CommentList.Comment...)
}
comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
v, _ := xml.Marshal(comments)
f.saveFileList(commentsXML, v)
}
// countComments provides a function to get comments files count storage in
// the folder xl.
func (f *File) countComments() int {
count := 0
for k := range f.XLSX {
if strings.Contains(k, "xl/comments") {
count++
}
}
return count
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
package excelize
import (
"fmt"
"strings"
)
// DataValidationType defined the type of data validation.
type DataValidationType int
// Data validation types.
const (
_DataValidationType = iota
typeNone // inline use
DataValidationTypeCustom
DataValidationTypeDate
DataValidationTypeDecimal
typeList // inline use
DataValidationTypeTextLeng
DataValidationTypeTime
// DataValidationTypeWhole Integer
DataValidationTypeWhole
)
const (
// dataValidationFormulaStrLen 255 characters+ 2 quotes
dataValidationFormulaStrLen = 257
// dataValidationFormulaStrLenErr
dataValidationFormulaStrLenErr = "data validation must be 0-255 characters"
)
// DataValidationErrorStyle defined the style of data validation error alert.
type DataValidationErrorStyle int
// Data validation error styles.
const (
_ DataValidationErrorStyle = iota
DataValidationErrorStyleStop
DataValidationErrorStyleWarning
DataValidationErrorStyleInformation
)
// Data validation error styles.
const (
styleStop = "stop"
styleWarning = "warning"
styleInformation = "information"
)
// DataValidationOperator operator enum.
type DataValidationOperator int
// Data validation operators.
const (
_DataValidationOperator = iota
DataValidationOperatorBetween
DataValidationOperatorEqual
DataValidationOperatorGreaterThan
DataValidationOperatorGreaterThanOrEqual
DataValidationOperatorLessThan
DataValidationOperatorLessThanOrEqual
DataValidationOperatorNotBetween
DataValidationOperatorNotEqual
)
// NewDataValidation return data validation struct.
func NewDataValidation(allowBlank bool) *DataValidation {
return &DataValidation{
AllowBlank: allowBlank,
ShowErrorMessage: false,
ShowInputMessage: false,
}
}
// SetError set error notice.
func (dd *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) {
dd.Error = &msg
dd.ErrorTitle = &title
strStyle := styleStop
switch style {
case DataValidationErrorStyleStop:
strStyle = styleStop
case DataValidationErrorStyleWarning:
strStyle = styleWarning
case DataValidationErrorStyleInformation:
strStyle = styleInformation
}
dd.ShowErrorMessage = true
dd.ErrorStyle = &strStyle
}
// SetInput set prompt notice.
func (dd *DataValidation) SetInput(title, msg string) {
dd.ShowInputMessage = true
dd.PromptTitle = &title
dd.Prompt = &msg
}
// SetDropList data validation list.
func (dd *DataValidation) SetDropList(keys []string) error {
dd.Formula1 = "\"" + strings.Join(keys, ",") + "\""
dd.Type = convDataValidationType(typeList)
return nil
}
// SetRange provides function to set data validation range in drop list.
func (dd *DataValidation) SetRange(f1, f2 int, t DataValidationType, o DataValidationOperator) error {
formula1 := fmt.Sprintf("%d", f1)
formula2 := fmt.Sprintf("%d", f2)
if dataValidationFormulaStrLen < len(dd.Formula1) || dataValidationFormulaStrLen < len(dd.Formula2) {
return fmt.Errorf(dataValidationFormulaStrLenErr)
}
dd.Formula1 = formula1
dd.Formula2 = formula2
dd.Type = convDataValidationType(t)
dd.Operator = convDataValidationOperatior(o)
return nil
}
// SetSqrefDropList provides set data validation on a range with source
// reference range of the worksheet by given data validation object and
// worksheet name. The data validation object can be created by
// NewDataValidation function. For example, set data validation on
// Sheet1!A7:B8 with validation criteria source Sheet1!E1:E3 settings, create
// in-cell dropdown by allowing list source:
//
// dvRange := excelize.NewDataValidation(true)
// dvRange.Sqref = "A7:B8"
// dvRange.SetSqrefDropList("E1:E3", true)
// xlsx.AddDataValidation("Sheet1", dvRange)
//
func (dd *DataValidation) SetSqrefDropList(sqref string, isCurrentSheet bool) error {
if isCurrentSheet {
dd.Formula1 = sqref
dd.Type = convDataValidationType(typeList)
return nil
}
return fmt.Errorf("cross-sheet sqref cell are not supported")
}
// SetSqref provides function to set data validation range in drop list.
func (dd *DataValidation) SetSqref(sqref string) {
if dd.Sqref == "" {
dd.Sqref = sqref
} else {
dd.Sqref = fmt.Sprintf("%s %s", dd.Sqref, sqref)
}
}
// convDataValidationType get excel data validation type.
func convDataValidationType(t DataValidationType) string {
typeMap := map[DataValidationType]string{
typeNone: "none",
DataValidationTypeCustom: "custom",
DataValidationTypeDate: "date",
DataValidationTypeDecimal: "decimal",
typeList: "list",
DataValidationTypeTextLeng: "textLength",
DataValidationTypeTime: "time",
DataValidationTypeWhole: "whole",
}
return typeMap[t]
}
// convDataValidationOperatior get excel data validation operator.
func convDataValidationOperatior(o DataValidationOperator) string {
typeMap := map[DataValidationOperator]string{
DataValidationOperatorBetween: "between",
DataValidationOperatorEqual: "equal",
DataValidationOperatorGreaterThan: "greaterThan",
DataValidationOperatorGreaterThanOrEqual: "greaterThanOrEqual",
DataValidationOperatorLessThan: "lessThan",
DataValidationOperatorLessThanOrEqual: "lessThanOrEqual",
DataValidationOperatorNotBetween: "notBetween",
DataValidationOperatorNotEqual: "notEqual",
}
return typeMap[o]
}
// AddDataValidation provides set data validation on a range of the worksheet
// by given data validation object and worksheet name. The data validation
// object can be created by NewDataValidation function.
//
// Example 1, set data validation on Sheet1!A1:B2 with validation criteria
// settings, show error alert after invalid data is entered with "Stop" style
// and custom title "error body":
//
// dvRange := excelize.NewDataValidation(true)
// dvRange.Sqref = "A1:B2"
// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween)
// dvRange.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body")
// xlsx.AddDataValidation("Sheet1", dvRange)
//
// Example 2, set data validation on Sheet1!A3:B4 with validation criteria
// settings, and show input message when cell is selected:
//
// dvRange = excelize.NewDataValidation(true)
// dvRange.Sqref = "A3:B4"
// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan)
// dvRange.SetInput("input title", "input body")
// xlsx.AddDataValidation("Sheet1", dvRange)
//
// Example 3, set data validation on Sheet1!A5:B6 with validation criteria
// settings, create in-cell dropdown by allowing list source:
//
// dvRange = excelize.NewDataValidation(true)
// dvRange.Sqref = "A5:B6"
// dvRange.SetDropList([]string{"1", "2", "3"})
// xlsx.AddDataValidation("Sheet1", dvRange)
//
func (f *File) AddDataValidation(sheet string, dv *DataValidation) {
xlsx := f.workSheetReader(sheet)
if nil == xlsx.DataValidations {
xlsx.DataValidations = new(xlsxDataValidations)
}
xlsx.DataValidations.DataValidation = append(xlsx.DataValidations.DataValidation, dv)
xlsx.DataValidations.Count = len(xlsx.DataValidations.DataValidation)
}
+151
View File
@@ -0,0 +1,151 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
package excelize
import (
"math"
"time"
)
// timeLocationUTC defined the UTC time location.
var timeLocationUTC, _ = time.LoadLocation("UTC")
// timeToUTCTime provides a function to convert time to UTC time.
func timeToUTCTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
}
// timeToExcelTime provides a function to convert time to Excel time.
func timeToExcelTime(t time.Time) float64 {
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime
var excelTime float64
var deltaDays int64
excelTime = 0
deltaDays = 290 * 364
// check if UnixNano would be out of int64 range
for t.Unix() > deltaDays*24*60*60 {
// reduce by aprox. 290 years, which is max for int64 nanoseconds
delta := time.Duration(deltaDays) * 24 * time.Hour
excelTime = excelTime + float64(deltaDays)
t = t.Add(-delta)
}
// finally add remainder of UnixNano to keep nano precision
// and 25569 which is days between 1900 and 1970
return excelTime + float64(t.UnixNano())/8.64e13 + 25569.0
}
// shiftJulianToNoon provides a function to process julian date to noon.
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
julianDays++
julianFraction -= 0.5
case julianFraction <= -0.5:
julianDays--
julianFraction += 1.5
}
return julianDays, julianFraction
}
// fractionOfADay provides a function to return the integer values for hour,
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
const (
c1us = 1e3
c1s = 1e9
c1day = 24 * 60 * 60 * c1s
)
frac := int64(c1day*fraction + c1us/2)
nanoseconds = int((frac%c1s)/c1us) * c1us
frac /= c1s
seconds = int(frac % 60)
frac /= 60
minutes = int(frac % 60)
hours = int(frac / 60)
return
}
// julianDateToGregorianTime provides a function to convert julian date to
// gregorian time.
func julianDateToGregorianTime(part1, part2 float64) time.Time {
part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2)
julianDays := part1I + part2I
julianFraction := part1F + part2F
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
}
// doTheFliegelAndVanFlandernAlgorithm; By this point generations of
// programmers have repeated the algorithm sent to the editor of
// "Communications of the ACM" in 1968 (published in CACM, volume 11, number
// 10, October 1968, p.657). None of those programmers seems to have found it
// necessary to explain the constants or variable names set out by Henry F.
// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and
// expand an explanation here - that day is not today.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
n := (4 * l) / 146097
l = l - (146097*n+3)/4
i := (4000 * (l + 1)) / 1461001
l = l - (1461*i)/4 + 31
j := (80 * l) / 2447
d := l - (2447*j)/80
l = j / 11
m := j + 2 - (12 * l)
y := 100*(n-49) + i + l
return d, m, y
}
// timeFromExcelTime provides a function to convert an excelTime
// representation (stored as a floating point number) to a time.Time.
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
const MDD int64 = 106750 // Max time.Duration Days, aprox. 290 years
var date time.Time
var intPart = int64(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
if intPart <= 61 {
const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0
const MJD0 float64 = 2400000.5
var date time.Time
if date1904 {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
} else {
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
}
return date
}
var floatPart = excelTime - float64(intPart)
var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
if date1904 {
date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
} else {
date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
}
// Duration is limited to aprox. 290 years
for intPart > MDD {
durationDays := time.Duration(MDD) * time.Hour * 24
date = date.Add(durationDays)
intPart = intPart - MDD
}
durationDays := time.Duration(intPart) * time.Hour * 24
durationPart := time.Duration(dayNanoSeconds * floatPart)
return date.Add(durationDays).Add(durationPart)
}
+456
View File
@@ -0,0 +1,456 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
// Package excelize providing a set of functions that allow you to write to
// and read from XLSX files. Support reads and writes XLSX file generated by
// Microsoft Excel™ 2007 and later. Support save file without losing original
// charts of XLSX. This library needs Go version 1.8 or later.
//
// See https://xuri.me/excelize for more information about this package.
package excelize
import (
"archive/zip"
"bytes"
"encoding/xml"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
)
// File define a populated XLSX file struct.
type File struct {
checked map[string]bool
sheetMap map[string]string
ContentTypes *xlsxTypes
Path string
SharedStrings *xlsxSST
Sheet map[string]*xlsxWorksheet
SheetCount int
Styles *xlsxStyleSheet
Theme *xlsxTheme
WorkBook *xlsxWorkbook
WorkBookRels *xlsxWorkbookRels
XLSX map[string][]byte
}
// OpenFile take the name of an XLSX file and returns a populated XLSX file
// struct for it.
func OpenFile(filename string) (*File, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
f, err := OpenReader(file)
if err != nil {
return nil, err
}
f.Path = filename
return f, nil
}
// OpenReader take an io.Reader and return a populated XLSX file.
func OpenReader(r io.Reader) (*File, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
if err != nil {
return nil, err
}
file, sheetCount, err := ReadZipReader(zr)
if err != nil {
return nil, err
}
f := &File{
checked: make(map[string]bool),
Sheet: make(map[string]*xlsxWorksheet),
SheetCount: sheetCount,
XLSX: file,
}
f.sheetMap = f.getSheetMap()
f.Styles = f.stylesReader()
f.Theme = f.themeReader()
return f, nil
}
// setDefaultTimeStyle provides a function to set default numbers format for
// time.Time type cell value by given worksheet name, cell coordinates and
// number format code.
func (f *File) setDefaultTimeStyle(sheet, axis string, format int) {
if f.GetCellStyle(sheet, axis) == 0 {
style, _ := f.NewStyle(`{"number_format": ` + strconv.Itoa(format) + `}`)
f.SetCellStyle(sheet, axis, axis, style)
}
}
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
func (f *File) workSheetReader(sheet string) *xlsxWorksheet {
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
name = "xl/worksheets/" + strings.ToLower(sheet) + ".xml"
}
if f.Sheet[name] == nil {
var xlsx xlsxWorksheet
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(name)), &xlsx)
if f.checked == nil {
f.checked = make(map[string]bool)
}
ok := f.checked[name]
if !ok {
checkSheet(&xlsx)
checkRow(&xlsx)
f.checked[name] = true
}
f.Sheet[name] = &xlsx
}
return f.Sheet[name]
}
// checkSheet provides a function to fill each row element and make that is
// continuous in a worksheet of XML.
func checkSheet(xlsx *xlsxWorksheet) {
row := len(xlsx.SheetData.Row)
if row >= 1 {
lastRow := xlsx.SheetData.Row[row-1].R
if lastRow >= row {
row = lastRow
}
}
sheetData := xlsxSheetData{}
existsRows := map[int]int{}
for k := range xlsx.SheetData.Row {
existsRows[xlsx.SheetData.Row[k].R] = k
}
for i := 0; i < row; i++ {
_, ok := existsRows[i+1]
if ok {
sheetData.Row = append(sheetData.Row, xlsx.SheetData.Row[existsRows[i+1]])
} else {
sheetData.Row = append(sheetData.Row, xlsxRow{
R: i + 1,
})
}
}
xlsx.SheetData = sheetData
}
// replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
// Office Excel 2007.
func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
var newXmlns = []byte(`<worksheet xr:uid="{00000000-0001-0000-0000-000000000000}" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac xr xr2 xr3" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
return workbookMarshal
}
// UpdateLinkedValue fix linked values within a spreadsheet are not updating in
// Office Excel 2007 and 2010. This function will be remove value tag when met a
// cell have a linked value. Reference
// https://social.technet.microsoft.com/Forums/office/en-US/e16bae1f-6a2c-4325-8013-e989a3479066/excel-2010-linked-cells-not-updating?forum=excel
//
// Notice: after open XLSX file Excel will be update linked value and generate
// new value and will prompt save file or not.
//
// For example:
//
// <row r="19" spans="2:2">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// <v>100</v>
// </c>
// </row>
//
// to
//
// <row r="19" spans="2:2">
// <c r="B19">
// <f>SUM(Sheet2!D2,Sheet2!D11)</f>
// </c>
// </row>
//
func (f *File) UpdateLinkedValue() {
for _, name := range f.GetSheetMap() {
xlsx := f.workSheetReader(name)
for indexR := range xlsx.SheetData.Row {
for indexC, col := range xlsx.SheetData.Row[indexR].C {
if col.F != nil && col.V != "" {
xlsx.SheetData.Row[indexR].C[indexC].V = ""
xlsx.SheetData.Row[indexR].C[indexC].T = ""
}
}
}
}
}
// adjustHelper provides a function to adjust rows and columns dimensions,
// hyperlinks, merged cells and auto filter when inserting or deleting rows or
// columns.
//
// sheet: Worksheet name that we're editing
// column: Index number of the column we're inserting/deleting before
// row: Index number of the row we're inserting/deleting before
// offset: Number of rows/column to insert/delete negative values indicate deletion
//
// TODO: adjustPageBreaks, adjustComments, adjustDataValidations, adjustProtectedCells
//
func (f *File) adjustHelper(sheet string, column, row, offset int) {
xlsx := f.workSheetReader(sheet)
f.adjustRowDimensions(xlsx, row, offset)
f.adjustColDimensions(xlsx, column, offset)
f.adjustHyperlinks(sheet, column, row, offset)
f.adjustMergeCells(xlsx, column, row, offset)
f.adjustAutoFilter(xlsx, column, row, offset)
checkSheet(xlsx)
checkRow(xlsx)
}
// adjustColDimensions provides a function to update column dimensions when
// inserting or deleting rows or columns.
func (f *File) adjustColDimensions(xlsx *xlsxWorksheet, column, offset int) {
for i, r := range xlsx.SheetData.Row {
for k, v := range r.C {
axis := v.R
col := string(strings.Map(letterOnlyMapF, axis))
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
yAxis := TitleToNumber(col)
if yAxis >= column && column != -1 {
xlsx.SheetData.Row[i].C[k].R = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
}
}
}
}
// adjustRowDimensions provides a function to update row dimensions when
// inserting or deleting rows or columns.
func (f *File) adjustRowDimensions(xlsx *xlsxWorksheet, rowIndex, offset int) {
if rowIndex == -1 {
return
}
for i, r := range xlsx.SheetData.Row {
if r.R >= rowIndex {
f.ajustSingleRowDimensions(&xlsx.SheetData.Row[i], offset)
}
}
}
// ajustSingleRowDimensions provides a function to ajust single row
// dimensions.
func (f *File) ajustSingleRowDimensions(r *xlsxRow, offset int) {
r.R += offset
for i, col := range r.C {
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, col.R))
r.C[i].R = string(strings.Map(letterOnlyMapF, col.R)) + strconv.Itoa(row+offset)
}
}
// adjustHyperlinks provides a function to update hyperlinks when inserting or
// deleting rows or columns.
func (f *File) adjustHyperlinks(sheet string, column, rowIndex, offset int) {
xlsx := f.workSheetReader(sheet)
// order is important
if xlsx.Hyperlinks != nil && offset < 0 {
for i, v := range xlsx.Hyperlinks.Hyperlink {
axis := v.Ref
col := string(strings.Map(letterOnlyMapF, axis))
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
yAxis := TitleToNumber(col)
if row == rowIndex || yAxis == column {
f.deleteSheetRelationships(sheet, v.RID)
if len(xlsx.Hyperlinks.Hyperlink) > 1 {
xlsx.Hyperlinks.Hyperlink = append(xlsx.Hyperlinks.Hyperlink[:i], xlsx.Hyperlinks.Hyperlink[i+1:]...)
} else {
xlsx.Hyperlinks = nil
}
}
}
}
if xlsx.Hyperlinks != nil {
for i, v := range xlsx.Hyperlinks.Hyperlink {
axis := v.Ref
col := string(strings.Map(letterOnlyMapF, axis))
row, _ := strconv.Atoi(strings.Map(intOnlyMapF, axis))
xAxis := row + offset
yAxis := TitleToNumber(col)
if rowIndex != -1 && row >= rowIndex {
xlsx.Hyperlinks.Hyperlink[i].Ref = col + strconv.Itoa(xAxis)
}
if column != -1 && yAxis >= column {
xlsx.Hyperlinks.Hyperlink[i].Ref = ToAlphaString(yAxis+offset) + strconv.Itoa(row)
}
}
}
}
// adjustMergeCellsHelper provides a function to update merged cells when
// inserting or deleting rows or columns.
func (f *File) adjustMergeCellsHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
if xlsx.MergeCells != nil {
for k, v := range xlsx.MergeCells.Cells {
beg := strings.Split(v.Ref, ":")[0]
end := strings.Split(v.Ref, ":")[1]
begcol := string(strings.Map(letterOnlyMapF, beg))
begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
begxAxis := begrow + offset
begyAxis := TitleToNumber(begcol)
endcol := string(strings.Map(letterOnlyMapF, end))
endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
endxAxis := endrow + offset
endyAxis := TitleToNumber(endcol)
if rowIndex != -1 {
if begrow > 1 && begrow >= rowIndex {
beg = begcol + strconv.Itoa(begxAxis)
}
if endrow > 1 && endrow >= rowIndex {
end = endcol + strconv.Itoa(endxAxis)
}
}
if column != -1 {
if begyAxis >= column {
beg = ToAlphaString(begyAxis+offset) + strconv.Itoa(endrow)
}
if endyAxis >= column {
end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
}
}
xlsx.MergeCells.Cells[k].Ref = beg + ":" + end
}
}
}
// adjustMergeCells provides a function to update merged cells when inserting
// or deleting rows or columns.
func (f *File) adjustMergeCells(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
f.adjustMergeCellsHelper(xlsx, column, rowIndex, offset)
if xlsx.MergeCells != nil && offset < 0 {
for k, v := range xlsx.MergeCells.Cells {
beg := strings.Split(v.Ref, ":")[0]
end := strings.Split(v.Ref, ":")[1]
if beg == end {
xlsx.MergeCells.Count += offset
if len(xlsx.MergeCells.Cells) > 1 {
xlsx.MergeCells.Cells = append(xlsx.MergeCells.Cells[:k], xlsx.MergeCells.Cells[k+1:]...)
} else {
xlsx.MergeCells = nil
}
}
}
}
}
// adjustAutoFilter provides a function to update the auto filter when
// inserting or deleting rows or columns.
func (f *File) adjustAutoFilter(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
f.adjustAutoFilterHelper(xlsx, column, rowIndex, offset)
if xlsx.AutoFilter != nil {
beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
begcol := string(strings.Map(letterOnlyMapF, beg))
begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
begxAxis := begrow + offset
endcol := string(strings.Map(letterOnlyMapF, end))
endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
endxAxis := endrow + offset
endyAxis := TitleToNumber(endcol)
if rowIndex != -1 {
if begrow >= rowIndex {
beg = begcol + strconv.Itoa(begxAxis)
}
if endrow >= rowIndex {
end = endcol + strconv.Itoa(endxAxis)
}
}
if column != -1 && endyAxis >= column {
end = ToAlphaString(endyAxis+offset) + strconv.Itoa(endrow)
}
xlsx.AutoFilter.Ref = beg + ":" + end
}
}
// adjustAutoFilterHelper provides a function to update the auto filter when
// inserting or deleting rows or columns.
func (f *File) adjustAutoFilterHelper(xlsx *xlsxWorksheet, column, rowIndex, offset int) {
if xlsx.AutoFilter != nil {
beg := strings.Split(xlsx.AutoFilter.Ref, ":")[0]
end := strings.Split(xlsx.AutoFilter.Ref, ":")[1]
begcol := string(strings.Map(letterOnlyMapF, beg))
begrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, beg))
begyAxis := TitleToNumber(begcol)
endcol := string(strings.Map(letterOnlyMapF, end))
endyAxis := TitleToNumber(endcol)
endrow, _ := strconv.Atoi(strings.Map(intOnlyMapF, end))
if (begrow == rowIndex && offset < 0) || (column == begyAxis && column == endyAxis) {
xlsx.AutoFilter = nil
for i, r := range xlsx.SheetData.Row {
if begrow < r.R && r.R <= endrow {
xlsx.SheetData.Row[i].Hidden = false
}
}
}
}
}
// GetMergeCells provides a function to get all merged cells from a worksheet currently.
func (f *File) GetMergeCells(sheet string) []MergeCell {
mergeCells := []MergeCell{}
xlsx := f.workSheetReader(sheet)
if xlsx.MergeCells != nil {
for i := 0; i < len(xlsx.MergeCells.Cells); i++ {
ref := xlsx.MergeCells.Cells[i].Ref
axis := strings.Split(ref, ":")[0]
mergeCells = append(mergeCells, []string{ref, f.GetCellValue(sheet, axis)})
}
}
return mergeCells
}
// MergeCell define a merged cell data.
// It consists of the following structure.
// example: []string{"D4:E10", "cell value"}
type MergeCell []string
// GetCellValue returns merged cell value.
func (m *MergeCell) GetCellValue() string {
return (*m)[1]
}
// GetStartAxis returns the merge start axis.
// example: "C2"
func (m *MergeCell) GetStartAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[0]
}
// GetEndAxis returns the merge end axis.
// example: "D4"
func (m *MergeCell) GetEndAxis() string {
axis := strings.Split((*m)[0], ":")
return axis[1]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

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