Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0318c95055 | |||
| f2f1fcc9aa | |||
| b755519fc6 | |||
| 3455fb0a1c | |||
| 5bb9ed77b8 | |||
| 678686fd48 | |||
| 9766648536 | |||
| 2cf20e7206 | |||
| 8cac1f5393 | |||
| 8337cdec0c | |||
| 46cecc47ea | |||
| 7535300107 | |||
| be41a70c76 | |||
| 5b407824a5 | |||
| 56f66fcaed | |||
| a298cb3d52 | |||
| 68f2c0fd8f | |||
| d314891126 | |||
| c0ae31f499 | |||
| 25edb6b5b7 | |||
| ead64dc776 | |||
| 876e3b1f45 | |||
| d6e940803d | |||
| 203e72f8a2 | |||
| 541c82410e | |||
| b680112fdb | |||
| 4ab5ab9ff4 | |||
| b425024359 | |||
| 78337c14ec | |||
| cdba8af129 | |||
| 9a7426180d | |||
| b7131603c4 | |||
| 2f3a5a0a59 | |||
| c2468a7389 | |||
| b6ba3486d6 | |||
| f59e909d30 | |||
| 3bb47de45a | |||
| c71973850e | |||
| 65f062b860 | |||
| 7b502fed36 | |||
| fd6b15bdaf | |||
| f31bf24ec2 | |||
| 30edb4d9fa | |||
| a32c3a1589 | |||
| 127e027940 | |||
| 2ee0838d8a | |||
| 72c23499f2 | |||
| 96d8868694 | |||
| 1533a57cb8 | |||
| 615f52d2e4 | |||
| f7dfd4ec77 | |||
| 2276aa12ef | |||
| 6505594fd0 | |||
| c55e26021b | |||
| 78420a692f | |||
| 6d9af149ad | |||
| 253cad35ef | |||
| 47c8736f34 | |||
| dcce8ace9e | |||
| 125ccc5c3b | |||
| 432d59d483 | |||
| 60f222b011 | |||
| 7a8b86ed12 | |||
| b940afa6b0 | |||
| 8afd00ff86 | |||
| 8992fd6d3a | |||
| 524a892480 | |||
| c420e23edb | |||
| 7419e03d67 | |||
| 9425544d1c | |||
| a8d78832df | |||
| adfb2236dc | |||
| 6034598bb4 | |||
| 6fe44cb1cb | |||
| 39d67d1775 | |||
| a8f9f12bb8 | |||
| 2c1abb1d11 | |||
| 21e0219568 | |||
| 952c3a1243 | |||
| 33cd7ebd6b | |||
| be02b3418d | |||
| f390617167 | |||
| f7410200e3 | |||
| 9cfdfbac78 | |||
| b015e78fa0 | |||
| bb9092c3e5 | |||
| cc1bad8d3f | |||
| 8f7380d796 | |||
| f37b9aee70 | |||
| 1ab9027c93 | |||
| 96d341aaab | |||
| 1f25f511cc | |||
| 07c7a628d1 | |||
| d7a59845eb | |||
| 98619083a1 | |||
| c8a9038efe | |||
| b638d8bd65 | |||
| 77753a123f | |||
| 2c9d475a45 | |||
| 3540209b26 | |||
| 797681d76c | |||
| 0ec83f9a95 | |||
| ec798bd624 | |||
| 6b57201e59 | |||
| 4e35dfe6ed | |||
| 970bdb0157 | |||
| d656faf915 | |||
| 2ecc70288e | |||
| ce95cdc021 | |||
| 22130c7afe | |||
| d85c65faef | |||
| c9f33eefec | |||
| 1826e76a34 | |||
| b9a6b7af22 | |||
| 95bfea4feb | |||
| e4bb8ac94a | |||
| d7a49bf56d | |||
| 3d1a1670cc | |||
| 7de6a79611 | |||
| 83198b39cb | |||
| db0724f09b | |||
| c1b013c926 | |||
| 40bc84ea3d | |||
| e977a97013 | |||
| 9330f7f5ea | |||
| d20bcc162c | |||
| b48ad793da | |||
| 3754734130 | |||
| 918b88332b | |||
| 9ba182d65c | |||
| 8cf54ce25b | |||
| 22d00739ff | |||
| 3b2a317d2b | |||
| afa9b173ba | |||
| 53f24c033c | |||
| 1ffcaaef8e | |||
| 62ba00270a | |||
| fe31335cf5 | |||
| b709b58fef | |||
| 76b220aa5d | |||
| dbf8f91696 | |||
| 335e8730da | |||
| cbed318832 | |||
| 31a6568cbe | |||
| 2aa86361e1 | |||
| 2a21dad90a | |||
| bae9f60012 | |||
| 79d1189803 | |||
| c3d0c8921b | |||
| 1da98a058b | |||
| 152386c5a0 | |||
| 5e2845d3a1 | |||
| bab49c9a66 | |||
| 1d3bfcaee5 | |||
| a37a33321f | |||
| 48042ecce4 | |||
| 5d8227d8bb | |||
| 38d3d77c59 | |||
| fec195b3a1 | |||
| 5945ed64c6 | |||
| ce515d991a | |||
| cb04dc6c34 | |||
| 17905c2d21 | |||
| b7c243823a | |||
| ebea14c74f | |||
| d6ef790010 | |||
| 5c64580378 | |||
| e49164fa81 | |||
| 8265e24add | |||
| 283985df52 | |||
| 95adca04bd | |||
| 2e65138222 | |||
| 87039524a7 | |||
| 66cd21c35f | |||
| 7e64131931 | |||
| e30d40cfbb | |||
| 751ed0003d | |||
| 6d9f89a1d4 | |||
| bc329be03b | |||
| 4597a0a50d | |||
| 3dda86d170 | |||
| d847da7591 | |||
| e1eca90835 | |||
| 9283b8c284 | |||
| d8d9afa4d2 | |||
| 4d3829c345 | |||
| a23037a437 | |||
| 181295e54e | |||
| 8cc2499e21 | |||
| a2c49452b4 | |||
| ebf0df5ca8 | |||
| 73bf691ccc | |||
| b2e9701826 | |||
| 789b0a14d1 | |||
| 3ee64fcd8c | |||
| 527503b0d9 | |||
| a95b2fccd7 | |||
| 7e2abb43e3 | |||
| 48fd753a96 | |||
| 136232dd57 | |||
| 008f19355c | |||
| 1fc0a5bbbc | |||
| ce099b8fc8 | |||
| 9c00ac6ba1 | |||
| a5c002bbce | |||
| cf50a699a6 | |||
| 662003c0ca | |||
| f24de2562a | |||
| b84bd7e16f | |||
| 2fb6abf53a | |||
| 2eab29f428 | |||
| 84ee0d259f | |||
| eccab42fc8 | |||
| 1465ba36d3 | |||
| 8380b097b2 | |||
| 6b8f901163 | |||
| bb16f0a75b | |||
| 84dbc5d71e | |||
| 166eeee64c | |||
| 7a649fd652 | |||
| 94ae568526 | |||
| 9ec597f26c | |||
| 388bc5006d | |||
| 0e33c2ad9d | |||
| af726fbcfb | |||
| 1e82a5964d | |||
| d62ba8b0cd | |||
| 320ebe25ec | |||
| 68257d1742 | |||
| 80d167cb1b | |||
| cd911e8fa4 | |||
| faa6fcb00c | |||
| 836c1f461f | |||
| 97b2f54383 | |||
| 9d5f8438c5 | |||
| 3f7963e7cf | |||
| 269df85d27 | |||
| d28c6863e4 | |||
| bba4990ccc | |||
| c57afd6a5d | |||
| 25f355c8d0 | |||
| b1b0673e69 | |||
| 72c086d726 | |||
| 3810ba6c0b | |||
| 5c775b4539 | |||
| deb3e54cc3 | |||
| d1a8f6c668 | |||
| 2291ba7402 | |||
| 20390091f2 | |||
| 123d819b0f | |||
| 30e275a27d | |||
| 1a53f587e8 | |||
| 9c3ba81135 | |||
| 62cf625fe9 | |||
| e1f4876621 | |||
| b480659a22 | |||
| 09b1d37ae3 | |||
| 47b28e7b57 | |||
| 9cd7ad34b3 | |||
| d736a74198 | |||
| ada4a926c0 | |||
| 4ad401bafc | |||
| 05c8159601 | |||
| efb6cb980e | |||
| 00ad6487b6 | |||
| 9aa836a72c | |||
| 9ea6082d04 | |||
| 9e595ab6e8 | |||
| 8b6dd5fdad | |||
| 5c8df885a2 | |||
| cc4d91cdbb | |||
| 32167d4721 | |||
| 1077f09755 | |||
| 642bf97272 | |||
| 4038598470 | |||
| 79453d4d2a | |||
| 6aa2b74840 | |||
| a003612810 | |||
| d6c11a5d93 | |||
| fe085ce889 | |||
| 99933e02f4 | |||
| 9af232840c | |||
| 465ea889a2 | |||
| c46fdd8985 | |||
| 9fd0a5fd61 | |||
| 95489fcf49 | |||
| 0e2932ca89 | |||
| 764d88894e | |||
| 339302956a | |||
| 1101937028 | |||
| fc315064e7 | |||
| 740059075a | |||
| 770f0a94c9 | |||
| df07afb744 | |||
| 9bc930750d | |||
| 77ded19742 | |||
| 2632517fab | |||
| c8e926446e | |||
| 253f836571 | |||
| babf6595ee | |||
| 91dc207a78 | |||
| d334856c6c | |||
| 51b1eb3149 | |||
| 91d9a069ac | |||
| 2a4bf676c9 | |||
| 0723555b2d | |||
| a519249dfc | |||
| aa7ab49df9 | |||
| 5132e67921 | |||
| 3eb9b6193a | |||
| 09412ce519 | |||
| f22e059172 | |||
| 153a94374f | |||
| 749ee84ca3 | |||
| bb2311148e |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
|
||||
---
|
||||
name: 集成请求参数获取方法
|
||||
overview: 在 context.go 中为 Context 结构体添加五个请求参数获取方法:ReqData(统一获取)、ReqDataParams(URL参数)、ReqDataJson(JSON Body)、ReqDataForm(表单数据)、ReqFile(获取上传文件)。
|
||||
todos:
|
||||
- id: add-imports
|
||||
content: 在 context.go 中添加 bytes、io、mime/multipart 包的导入
|
||||
status: completed
|
||||
- id: impl-params
|
||||
content: 实现 ReqParam/ReqParams 方法(获取 URL 参数,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-form
|
||||
content: 实现 ReqForm/ReqForms 方法(获取表单数据,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-json
|
||||
content: 实现 ReqJson/ReqJsons 方法(获取 JSON Body,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-file
|
||||
content: 实现 ReqFile/ReqFiles 方法(获取上传文件)
|
||||
status: completed
|
||||
dependencies:
|
||||
- add-imports
|
||||
- id: impl-reqdata
|
||||
content: 实现 ReqData/ReqDatas 方法(统一获取,返回 *Obj)
|
||||
status: completed
|
||||
dependencies:
|
||||
- impl-params
|
||||
- impl-form
|
||||
- impl-json
|
||||
---
|
||||
|
||||
# 集成请求参数获取方法
|
||||
|
||||
## 实现位置
|
||||
|
||||
在 [`context.go`](context.go) 中添加请求参数获取方法,**风格与 `Session("key")` 保持一致,返回 `*Obj` 支持链式调用**。
|
||||
|
||||
## 新增方法
|
||||
|
||||
### 1. ReqParam - 获取 URL 查询参数(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取单个参数,支持链式调用
|
||||
func (that *Context) ReqParam(key string) *Obj {
|
||||
// that.ReqParam("id").ToStr()
|
||||
// that.ReqParam("id").ToInt()
|
||||
}
|
||||
|
||||
// 获取所有 URL 参数
|
||||
func (that *Context) ReqParams() Map
|
||||
```
|
||||
|
||||
### 2. ReqForm - 获取表单数据(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取单个表单字段
|
||||
func (that *Context) ReqForm(key string) *Obj {
|
||||
// that.ReqForm("name").ToStr()
|
||||
}
|
||||
|
||||
// 获取所有表单数据
|
||||
func (that *Context) ReqForms() Map
|
||||
```
|
||||
|
||||
### 3. ReqJson - 获取 JSON Body(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 获取 JSON 中的单个字段
|
||||
func (that *Context) ReqJson(key string) *Obj {
|
||||
// that.ReqJson("data").ToMap()
|
||||
// that.ReqJson("count").ToInt()
|
||||
}
|
||||
|
||||
// 获取完整 JSON Body
|
||||
func (that *Context) ReqJsons() Map
|
||||
```
|
||||
|
||||
### 4. ReqFile - 获取上传文件
|
||||
|
||||
```go
|
||||
func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error)
|
||||
func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error)
|
||||
```
|
||||
|
||||
### 5. ReqData - 统一获取参数(返回 *Obj)
|
||||
|
||||
```go
|
||||
// 统一获取(JSON > Form > URL),支持链式调用
|
||||
func (that *Context) ReqData(key string) *Obj {
|
||||
// that.ReqData("id").ToStr()
|
||||
}
|
||||
|
||||
// 获取所有合并后的参数
|
||||
func (that *Context) ReqDatas() Map
|
||||
```
|
||||
|
||||
## 需要的导入
|
||||
|
||||
```go
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
)
|
||||
```
|
||||
|
||||
## 关键实现细节
|
||||
|
||||
1. **Body 只能读取一次的问题**:读取 Body 后需要用 `io.NopCloser(bytes.NewBuffer(body))` 恢复,以便后续代码(如其他中间件)还能再次读取
|
||||
|
||||
2. **废弃 API 替换**:使用 `io.ReadAll` 替代已废弃的 `ioutil.ReadAll`
|
||||
|
||||
3. **多值参数处理**:当同一参数有多个值时(如 `?id=1&id=2`),存储为 `Slice`;单值则直接存储字符串
|
||||
|
||||
## 使用示例
|
||||
|
||||
```go
|
||||
appIns.Run(Router{
|
||||
"app": {
|
||||
"user": {
|
||||
"info": func(that *Context) {
|
||||
// 链式调用获取单个参数(类似 Session 风格)
|
||||
id := that.ReqData("id").ToInt() // 统一获取
|
||||
name := that.ReqParam("name").ToStr() // URL 参数
|
||||
age := that.ReqForm("age").ToCeilInt() // 表单参数
|
||||
data := that.ReqJson("profile").ToMap() // JSON 字段
|
||||
|
||||
// 获取所有参数(返回 Map)
|
||||
allParams := that.ReqDatas() // 合并后的所有参数
|
||||
urlParams := that.ReqParams() // 所有 URL 参数
|
||||
formData := that.ReqForms() // 所有表单数据
|
||||
jsonBody := that.ReqJsons() // 完整 JSON Body
|
||||
|
||||
that.Display(0, Map{"id": id, "name": name})
|
||||
},
|
||||
"upload": func(that *Context) {
|
||||
// 获取单个上传文件
|
||||
file, header, err := that.ReqFile("avatar")
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
// header.Filename - 文件名
|
||||
// header.Size - 文件大小
|
||||
}
|
||||
|
||||
// 获取多个同名上传文件
|
||||
files, err := that.ReqFiles("images")
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## 风格对比
|
||||
|
||||
| 旧方式(需要类型断言) | 新方式(链式调用) |
|
||||
|
||||
|------------------------|-------------------|
|
||||
|
||||
| `req["id"].(string) `| `that.ReqData("id").ToStr()` |
|
||||
|
||||
| `ObjToInt(req["id"]) `| `that.ReqData("id").ToInt()` |
|
||||
|
||||
| 需要手动处理 nil | `*Obj` 自动处理空值 |
|
||||
@@ -1 +1,6 @@
|
||||
/.idea/*
|
||||
.idea
|
||||
/example/config/app.json
|
||||
/example/tpt/demo/
|
||||
/example/config/
|
||||
/*.exe
|
||||
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/hotime.iml" filepath="$PROJECT_DIR$/.idea/hotime.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-163
@@ -1,163 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="b2aca021-ff30-4cbf-8dc9-8cdd4f4c39dc" name="Default Changelist" comment="" />
|
||||
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Go File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GOROOT" path="D:/app/go" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1TPrQqMMb4T7xS6v0afE7E8yl1y" />
|
||||
<component name="PropertiesComponent">
|
||||
<property name="DefaultGoTemplateProperty" value="Go File" />
|
||||
<property name="GO_FMT" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="aspect.path.notification.shown" value="true" />
|
||||
<property name="go.import.settings.migrated" value="true" />
|
||||
<property name="go.sdk.automatically.set" value="true" />
|
||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="D:\gopath\src\code.hoteas.com\hoteas\hotime\tools" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunDashboard">
|
||||
<option name="ruleStates">
|
||||
<list>
|
||||
<RuleState>
|
||||
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||
</RuleState>
|
||||
<RuleState>
|
||||
<option name="name" value="StatusDashboardGroupingRule" />
|
||||
</RuleState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="go build code.hoteas.com/hoteas/hotime/example" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="hotime" />
|
||||
<working_directory value="$PROJECT_DIR$/" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/example/main.go" />
|
||||
<package value="code.hoteas.com/hoteas/hotime/example" />
|
||||
<directory value="$PROJECT_DIR$/" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Go Build.go build code.hoteas.com/hoteas/hotime/example" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SvnConfiguration">
|
||||
<configuration />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="b2aca021-ff30-4cbf-8dc9-8cdd4f4c39dc" name="Default Changelist" comment="" />
|
||||
<created>1573372557346</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1573372557346</updated>
|
||||
<workItem from="1573372558521" duration="18000" />
|
||||
<workItem from="1573372583551" duration="5522000" />
|
||||
<workItem from="1582172338195" duration="5196000" />
|
||||
<workItem from="1582181939594" duration="501000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="清理">
|
||||
<created>1573372845218</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573372845218</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="清理">
|
||||
<created>1573372887175</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573372887175</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="自动设置数据库">
|
||||
<created>1573375369161</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375369161</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="自动设置数据库">
|
||||
<created>1573375745411</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375745411</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="自动设置数据库">
|
||||
<created>1573375887561</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573375887561</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="数据库缓存">
|
||||
<created>1573380045254</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1573380045254</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="增加跨域配置,以及增加配置说明">
|
||||
<created>1582179656809</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1582179656809</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="增加访问ip打印等功能">
|
||||
<created>1582182399676</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1582182399676</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="9" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="1" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State>
|
||||
<option name="COLUMN_ORDER" />
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="清理" />
|
||||
<MESSAGE value="自动设置数据库" />
|
||||
<MESSAGE value="数据库缓存" />
|
||||
<MESSAGE value="增加跨域配置,以及增加配置说明" />
|
||||
<MESSAGE value="增加访问ip打印等功能" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="增加访问ip打印等功能" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,208 @@
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
|
||||
AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution
|
||||
as defined by Sections 1 through 9 of that document.
|
||||
|
||||
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of that definition, "control" means (i) the power, direct
|
||||
or indirect, to cause the direction or management of such entity, whether
|
||||
by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
|
||||
of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
|
||||
granted by that License.
|
||||
|
||||
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation
|
||||
or translation of a Source form, including but not limited to compiled object
|
||||
code, generated documentation, and conversions to other media types.
|
||||
|
||||
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice that
|
||||
is included in or attached to the work (an example is provided in the Appendix
|
||||
below).
|
||||
|
||||
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of that License, Derivative
|
||||
Works shall not include works that remain separable from, or merely link (or
|
||||
bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative
|
||||
Works thereof, that is intentionally submitted to Licensor for inclusion in
|
||||
the Work by the copyright owner or by an individual or Legal Entity authorized
|
||||
to submit on behalf of the copyright owner. For the purposes of that definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication
|
||||
sent to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor
|
||||
for the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently incorporated
|
||||
within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of that
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
|
||||
Derivative Works of, publicly display, publicly perform, sublicense, and distribute
|
||||
the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of that License,
|
||||
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in that section) patent
|
||||
license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims
|
||||
licensable by such Contributor that are necessarily infringed by their Contribution(s)
|
||||
alone or by combination of their Contribution(s) with the Work to which such
|
||||
Contribution(s) was submitted. If You institute patent litigation against
|
||||
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that the Work or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses granted to You
|
||||
under that License for that Work shall terminate as of the date such litigation
|
||||
is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or
|
||||
Derivative Works thereof in any medium, with or without modifications, and
|
||||
in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy
|
||||
of that License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that
|
||||
You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source
|
||||
form of the Work, excluding those notices that do not pertain to any part
|
||||
of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable copy
|
||||
of the attribution notices contained within such NOTICE file, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works, in at least
|
||||
one of the following places: within a NOTICE text file distributed as part
|
||||
of the Derivative Works; within the Source form or documentation, if provided
|
||||
along with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works
|
||||
that You distribute, alongside or as an addendum to the NOTICE text from the
|
||||
Work, provided that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction,
|
||||
or distribution of Your modifications, or for any such Derivative Works as
|
||||
a whole, provided Your use, reproduction, and distribution of the Work otherwise
|
||||
complies with the conditions stated in that License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
||||
Contribution intentionally submitted for inclusion in the Work by You to the
|
||||
Licensor shall be under the terms and conditions of that License, without
|
||||
any additional terms or conditions. Notwithstanding the above, nothing herein
|
||||
shall supersede or modify the terms of any separate license agreement you
|
||||
may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. that License does not grant permission to use the trade names,
|
||||
trademarks, service marks, or product names of the Licensor, except as required
|
||||
for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
|
||||
in writing, Licensor provides the Work (and each Contributor provides its
|
||||
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
|
||||
of using or redistributing the Work and assume any risks associated with Your
|
||||
exercise of permissions under that License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether
|
||||
in tort (including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed to
|
||||
in writing, shall any Contributor be liable to You for damages, including
|
||||
any direct, indirect, special, incidental, or consequential damages of any
|
||||
character arising as a result of that License or out of the use or inability
|
||||
to use the Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all other commercial
|
||||
damages or losses), even if such Contributor has been advised of the possibility
|
||||
of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work
|
||||
or Derivative Works thereof, You may choose to offer, and charge a fee for,
|
||||
acceptance of support, warranty, indemnity, or other liability obligations
|
||||
and/or rights consistent with that License. However, in accepting such obligations,
|
||||
You may act only on Your own behalf and on Your sole responsibility, not on
|
||||
behalf of any other Contributor, and only if You agree to indemnify, defend,
|
||||
and hold each Contributor harmless for any liability incurred by, or claims
|
||||
asserted against, such Contributor by reason of your accepting any such warranty
|
||||
or additional liability. END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own identifying
|
||||
information. (Don't include the brackets!) The text should be enclosed in
|
||||
the appropriate comment syntax for the file format. We also recommend that
|
||||
a file or class name and description of purpose be included on the same "printed
|
||||
page" as the copyright notice for easier identification within third-party
|
||||
archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
you may not use that file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,226 @@
|
||||
# HoTime
|
||||
|
||||
**高性能 Go Web 服务框架**
|
||||
|
||||
## 特性
|
||||
|
||||
- **高性能** - 单机 10万+ QPS,支持百万级并发用户
|
||||
- **多数据库支持** - MySQL、SQLite3,支持主从分离
|
||||
- **三级缓存系统** - Memory > Redis > DB,自动穿透与回填
|
||||
- **Session管理** - 内置会话管理,支持多种存储后端
|
||||
- **自动代码生成** - 根据数据库表自动生成 CRUD 接口
|
||||
- **丰富工具类** - 上下文管理、类型转换、加密解密等
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
go get code.hoteas.com/golang/hotime
|
||||
```
|
||||
|
||||
### 最小示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appIns := Init("config/config.json")
|
||||
|
||||
appIns.Run(Router{
|
||||
"app": {
|
||||
"test": {
|
||||
"hello": func(that *Context) {
|
||||
that.Display(0, Map{"message": "Hello World"})
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
访问: http://localhost:8081/app/test/hello
|
||||
|
||||
## 性能测试报告
|
||||
|
||||
### 测试环境
|
||||
|
||||
| 项目 | 配置 |
|
||||
|------|------|
|
||||
| CPU | 24 核心 |
|
||||
| 系统 | Windows 10 |
|
||||
| Go 版本 | 1.19.3 |
|
||||
|
||||
### 测试结果
|
||||
|
||||
| 并发数 | QPS | 成功率 | 平均延迟 | P99延迟 |
|
||||
|--------|-----|--------|----------|---------|
|
||||
| 500 | 99,960 | 100% | 5.0ms | 25.2ms |
|
||||
| **1000** | **102,489** | **100%** | **9.7ms** | **56.8ms** |
|
||||
| 2000 | 75,801 | 99.99% | 26.2ms | 127.7ms |
|
||||
| 5000 | 12,611 | 99.95% | 391.4ms | 781.4ms |
|
||||
|
||||
### 性能总结
|
||||
|
||||
```
|
||||
最高 QPS: 102,489 请求/秒
|
||||
最佳并发数: 1,000
|
||||
```
|
||||
|
||||
### 并发用户估算
|
||||
|
||||
| 使用场景 | 请求频率 | 可支持用户数 |
|
||||
|----------|----------|--------------|
|
||||
| 高频交互 | 1次/秒 | ~10万 |
|
||||
| 活跃用户 | 1次/5秒 | ~50万 |
|
||||
| 普通浏览 | 1次/10秒 | ~100万 |
|
||||
| 低频访问 | 1次/30秒 | ~300万 |
|
||||
|
||||
**生产环境建议**: 保留 30-50% 性能余量,安全并发用户数约 **50万 - 70万**
|
||||
|
||||
### 与主流框架性能对比
|
||||
|
||||
| 框架 | 典型QPS | 基础实现 | 性能评级 |
|
||||
|------|---------|----------|----------|
|
||||
| **HoTime** | **~100K** | net/http | 第一梯队 |
|
||||
| Fiber | ~100K+ | fasthttp | 第一梯队 |
|
||||
| Gin | ~60-80K | net/http | 第二梯队 |
|
||||
| Echo | ~60-80K | net/http | 第二梯队 |
|
||||
| Chi | ~50-60K | net/http | 第二梯队 |
|
||||
|
||||
## 框架对比
|
||||
|
||||
### 功能特性对比
|
||||
|
||||
| 特性 | HoTime | Gin | Echo | Fiber |
|
||||
|------|--------|-----|------|-------|
|
||||
| 性能 | 100K QPS | 70K QPS | 70K QPS | 100K QPS |
|
||||
| 内置ORM | ✅ | ❌ | ❌ | ❌ |
|
||||
| 内置缓存 | ✅ 三级缓存 | ❌ | ❌ | ❌ |
|
||||
| Session | ✅ 内置 | ❌ 需插件 | ❌ 需插件 | ❌ 需插件 |
|
||||
| 代码生成 | ✅ | ❌ | ❌ | ❌ |
|
||||
| 微信/支付集成 | ✅ 内置 | ❌ | ❌ | ❌ |
|
||||
| 路由灵活性 | 中等 | 优秀 | 优秀 | 优秀 |
|
||||
| 社区生态 | 较小 | 庞大 | 较大 | 较大 |
|
||||
|
||||
### HoTime 优势
|
||||
|
||||
1. **开箱即用** - 内置 ORM + 缓存 + Session,无需额外集成
|
||||
2. **三级缓存** - Memory > Redis > DB,自动穿透与回填
|
||||
3. **开发效率高** - 链式查询语法简洁,内置微信/云服务SDK
|
||||
4. **性能优异** - 100K QPS,媲美最快的 Fiber 框架
|
||||
|
||||
### 适用场景
|
||||
|
||||
| 场景 | 推荐度 | 说明 |
|
||||
|------|--------|------|
|
||||
| 中小型后台系统 | ⭐⭐⭐⭐⭐ | 完美适配,开发效率最高 |
|
||||
| 微信小程序后端 | ⭐⭐⭐⭐⭐ | 内置微信SDK |
|
||||
| 快速原型开发 | ⭐⭐⭐⭐⭐ | 代码生成 + 全功能集成 |
|
||||
| 高并发API服务 | ⭐⭐⭐⭐ | 性能足够 |
|
||||
| 大型微服务 | ⭐⭐⭐ | 建议用Gin/Echo |
|
||||
|
||||
### 总体评价
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| 性能 | 95分 | 第一梯队,媲美Fiber |
|
||||
| 功能集成 | 90分 | 远超主流框架 |
|
||||
| 开发效率 | 85分 | 适合快速开发 |
|
||||
| 生态/社区 | 50分 | 持续建设中 |
|
||||
|
||||
> **总结**: HoTime 是"小而全"的高性能框架,性能不输主流,集成度远超主流,适合独立开发者或小团队快速构建中小型项目。
|
||||
|
||||
---
|
||||
|
||||
## 数据库操作
|
||||
|
||||
### 基础 CRUD
|
||||
|
||||
```go
|
||||
// 查询单条
|
||||
user := that.Db.Get("user", "*", Map{"id": 1})
|
||||
|
||||
// 查询列表
|
||||
users := that.Db.Select("user", "*", Map{"status": 1, "ORDER": "id DESC"})
|
||||
|
||||
// 插入数据
|
||||
id := that.Db.Insert("user", Map{"name": "test", "age": 18})
|
||||
|
||||
// 更新数据
|
||||
rows := that.Db.Update("user", Map{"name": "new"}, Map{"id": 1})
|
||||
|
||||
// 删除数据
|
||||
rows := that.Db.Delete("user", Map{"id": 1})
|
||||
```
|
||||
|
||||
### 链式查询
|
||||
|
||||
```go
|
||||
users := that.Db.Table("user").
|
||||
LeftJoin("order", "user.id=order.user_id").
|
||||
And("status", 1).
|
||||
Order("id DESC").
|
||||
Page(1, 10).
|
||||
Select("*")
|
||||
```
|
||||
|
||||
### 条件语法
|
||||
|
||||
| 语法 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| key | 等于 | "id": 1 |
|
||||
| key[>] | 大于 | "age[>]": 18 |
|
||||
| key[<] | 小于 | "age[<]": 60 |
|
||||
| key[!] | 不等于 | "status[!]": 0 |
|
||||
| key[~] | LIKE | "name[~]": "test" |
|
||||
| key[<>] | BETWEEN | "age[<>]": Slice{18, 60} |
|
||||
|
||||
## 缓存系统
|
||||
|
||||
三级缓存: **Memory > Redis > Database**
|
||||
|
||||
```go
|
||||
// 通用缓存
|
||||
that.Cache("key", value) // 设置
|
||||
data := that.Cache("key") // 获取
|
||||
that.Cache("key", nil) // 删除
|
||||
|
||||
// Session 缓存
|
||||
that.Session("user_id", 123) // 设置
|
||||
userId := that.Session("user_id") // 获取
|
||||
```
|
||||
|
||||
## 中间件
|
||||
|
||||
```go
|
||||
appIns.SetConnectListener(func(that *Context) bool {
|
||||
if that.Session("user_id").Data == nil {
|
||||
that.Display(2, "请先登录")
|
||||
return true // 终止请求
|
||||
}
|
||||
return false // 继续处理
|
||||
})
|
||||
```
|
||||
|
||||
## 扩展功能
|
||||
|
||||
- **微信支付/公众号/小程序** - dri/wechat/
|
||||
- **阿里云服务** - dri/aliyun/
|
||||
- **腾讯云服务** - dri/tencent/
|
||||
- **文件上传下载** - dri/upload/, dri/download/
|
||||
- **MongoDB** - dri/mongodb/
|
||||
- **RSA加解密** - dri/rsa/
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
---
|
||||
|
||||
**HoTime** - 让 Go Web 开发更简单、更高效
|
||||
+520
-192
@@ -1,13 +1,15 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "code.hoteas.com/golang/hotime/cache"
|
||||
"code.hoteas.com/golang/hotime/code"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
. "code.hoteas.com/golang/hotime/log"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -18,58 +20,81 @@ import (
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
MakeCodeRouter map[string]*code.MakeCode
|
||||
MethodRouter
|
||||
Router
|
||||
contextBase
|
||||
Error
|
||||
Log *logrus.Logger
|
||||
WebConnectLog *logrus.Logger
|
||||
Port string //端口号
|
||||
TLSPort string //ssl访问端口号
|
||||
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
||||
connectDbFunc func(err ...*Error) *sql.DB
|
||||
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
||||
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
|
||||
configPath string
|
||||
Config Map
|
||||
Db HoTimeDB
|
||||
Server *http.Server
|
||||
CacheIns
|
||||
sessionLong CacheIns
|
||||
sessionShort CacheIns
|
||||
*HoTimeCache
|
||||
*http.Server
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func (this *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
this.handler(w, req)
|
||||
func (that *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
that.handler(w, req)
|
||||
}
|
||||
|
||||
//启动实例
|
||||
func (this *Application) Run(router Router) {
|
||||
// Run 启动实例
|
||||
func (that *Application) Run(router Router) {
|
||||
//如果没有设置配置自动生成配置
|
||||
if this.configPath == "" || len(this.Config) == 0 {
|
||||
this.SetConfig()
|
||||
if that.configPath == "" || len(that.Config) == 0 {
|
||||
that.SetConfig()
|
||||
}
|
||||
|
||||
//防止手动设置缓存误伤
|
||||
if this.CacheIns == nil {
|
||||
this.SetCache(CacheIns(&CacheMemory{}))
|
||||
if that.HoTimeCache == nil {
|
||||
that.SetCache()
|
||||
}
|
||||
|
||||
//防止手动设置session误伤
|
||||
if this.sessionShort == nil && this.sessionLong == nil {
|
||||
if this.connectDbFunc == nil {
|
||||
this.SetSession(CacheIns(&CacheMemory{}), nil)
|
||||
} else {
|
||||
this.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &this.Db, Time: this.Config.GetInt64("cacheLongTime")}))
|
||||
//if that.sessionShort == nil && that.sessionLong == nil {
|
||||
// if that.connectDbFunc == nil {
|
||||
// that.SetSession(CacheIns(&CacheMemory{}), nil)
|
||||
// } else {
|
||||
// that.SetSession(CacheIns(&CacheMemory{}), CacheIns(&CacheDb{Db: &that.Db, Time: that.Config.GetInt64("cacheLongTime")}))
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//that.Router = router
|
||||
if that.Router == nil {
|
||||
that.Router = Router{}
|
||||
}
|
||||
for k, _ := range router {
|
||||
v := router[k]
|
||||
if that.Router[k] == nil {
|
||||
that.Router[k] = v
|
||||
}
|
||||
//直达接口层复用
|
||||
for k1, _ := range v {
|
||||
v1 := v[k1]
|
||||
if that.Router[k][k1] == nil {
|
||||
that.Router[k][k1] = v1
|
||||
}
|
||||
|
||||
for k2, _ := range v1 {
|
||||
v2 := v1[k2]
|
||||
that.Router[k][k1][k2] = v2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.Router = router
|
||||
//重新设置MethodRouter//直达路由
|
||||
this.MethodRouter = MethodRouter{}
|
||||
that.MethodRouter = MethodRouter{}
|
||||
modeRouterStrict := true
|
||||
if this.Config.Get("modeRouterStrict").(bool) == false {
|
||||
if that.Config.GetBool("modeRouterStrict") == false {
|
||||
modeRouterStrict = false
|
||||
}
|
||||
if router != nil {
|
||||
for pk, pv := range router {
|
||||
if that.Router != nil {
|
||||
for pk, pv := range that.Router {
|
||||
if !modeRouterStrict {
|
||||
pk = strings.ToLower(pk)
|
||||
}
|
||||
@@ -83,7 +108,7 @@ func (this *Application) Run(router Router) {
|
||||
if !modeRouterStrict {
|
||||
mk = strings.ToLower(mk)
|
||||
}
|
||||
this.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
|
||||
that.MethodRouter["/"+pk+"/"+ck+"/"+mk] = mv
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,185 +116,177 @@ func (this *Application) Run(router Router) {
|
||||
}
|
||||
}
|
||||
|
||||
//this.Port = port
|
||||
this.Port = this.Config.GetString("port")
|
||||
this.TLSPort = this.Config.GetString("tlsPort")
|
||||
//that.Port = port
|
||||
that.Port = that.Config.GetString("port")
|
||||
that.TLSPort = that.Config.GetString("tlsPort")
|
||||
|
||||
if this.connectDbFunc != nil && (this.Db.DB == nil || this.Db.DB.Ping() != nil) {
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
}
|
||||
|
||||
if this.CacheIns == nil {
|
||||
this.CacheIns = CacheIns(&CacheMemory{Map: Map{}, Time: this.Config.GetInt64("cacheShortTime")})
|
||||
if that.connectDbFunc != nil && (that.Db.DB == nil || that.Db.DB.Ping() != nil) {
|
||||
that.Db.SetConnect(that.connectDbFunc)
|
||||
}
|
||||
|
||||
//异常处理
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
this.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
|
||||
//log.Println(err)
|
||||
this.Run(router)
|
||||
//that.SetError(errors.New(fmt.Sprint(err)), LOG_FMT)
|
||||
that.Log.Warn(err)
|
||||
|
||||
that.Run(router)
|
||||
}
|
||||
}()
|
||||
|
||||
this.Server = &http.Server{}
|
||||
that.Server = &http.Server{}
|
||||
if !IsRun {
|
||||
IsRun = true
|
||||
}
|
||||
|
||||
ch := make(chan int)
|
||||
if ObjToCeilInt(this.Port) != 0 {
|
||||
if ObjToCeilInt(that.Port) != 0 {
|
||||
go func() {
|
||||
|
||||
App[this.Port] = this
|
||||
this.Server.Handler = this
|
||||
App[that.Port] = that
|
||||
that.Server.Handler = that
|
||||
//启动服务
|
||||
this.Server.Addr = ":" + this.Port
|
||||
err := this.Server.ListenAndServe()
|
||||
log.Println(err)
|
||||
that.Server.Addr = ":" + that.Port
|
||||
err := that.Server.ListenAndServe()
|
||||
that.Log.Error(err)
|
||||
ch <- 1
|
||||
|
||||
}()
|
||||
} else if ObjToCeilInt(this.TLSPort) != 0 {
|
||||
}
|
||||
|
||||
if ObjToCeilInt(that.TLSPort) != 0 {
|
||||
go func() {
|
||||
|
||||
App[this.TLSPort] = this
|
||||
this.Server.Handler = this
|
||||
App[that.TLSPort] = that
|
||||
that.Server.Handler = that
|
||||
//启动服务
|
||||
this.Server.Addr = ":" + this.TLSPort
|
||||
err := this.Server.ListenAndServeTLS(this.Config.GetString("tlsCert"), this.Config.GetString("tlsKey"))
|
||||
log.Println(err)
|
||||
that.Server.Addr = ":" + that.TLSPort
|
||||
err := that.Server.ListenAndServeTLS(that.Config.GetString("tlsCert"), that.Config.GetString("tlsKey"))
|
||||
that.Log.Error(err)
|
||||
ch <- 2
|
||||
|
||||
}()
|
||||
} else {
|
||||
log.Println("没有端口启用")
|
||||
}
|
||||
if ObjToCeilInt(that.Port) == 0 && ObjToCeilInt(that.TLSPort) == 0 {
|
||||
that.Log.Error("没有端口启用")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
value := <-ch
|
||||
|
||||
log.Println("启动服务失败 : ", value)
|
||||
that.Log.Error("启动服务失败 : " + ObjToStr(value))
|
||||
}
|
||||
|
||||
//启动实例
|
||||
func (this *Application) SetConnectDB(connect func(err ...*Error) *sql.DB) {
|
||||
// SetConnectDB 启动实例
|
||||
func (that *Application) SetConnectDB(connect func(err ...*Error) (master, slave *sql.DB)) {
|
||||
|
||||
//this.Db.DBCached=false
|
||||
//if this.Config.GetCeilInt("dbCached")!=0{
|
||||
// this.Db.DBCached=true
|
||||
//}
|
||||
that.connectDbFunc = connect
|
||||
|
||||
this.connectDbFunc = connect
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
|
||||
this.Db.DBCached = false
|
||||
if this.Config.GetCeilInt("dbCached") != 0 {
|
||||
this.Db.DBCached = true
|
||||
}
|
||||
|
||||
this.Db.Type = this.Config.GetString("dbType")
|
||||
that.Db.SetConnect(that.connectDbFunc, &that.Error)
|
||||
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetSession(short CacheIns, Long CacheIns) {
|
||||
this.sessionLong = Long
|
||||
this.sessionShort = short
|
||||
|
||||
}
|
||||
|
||||
//默认配置缓存和session实现
|
||||
func (this *Application) SetDefault(connect func(err ...*Error) *sql.DB) {
|
||||
this.SetConfig()
|
||||
// SetDefault 默认配置缓存和session实现
|
||||
func (that *Application) SetDefault(connect func(err ...*Error) (*sql.DB, *sql.DB)) {
|
||||
that.SetConfig()
|
||||
|
||||
if connect != nil {
|
||||
this.connectDbFunc = connect
|
||||
this.Db.SetConnect(this.connectDbFunc)
|
||||
that.connectDbFunc = connect
|
||||
that.Db.SetConnect(that.connectDbFunc)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetCache(cache CacheIns) {
|
||||
|
||||
this.CacheIns = cache
|
||||
|
||||
// SetCache 设置配置文件路径全路径或者相对路径
|
||||
func (that *Application) SetCache() {
|
||||
cacheIns := HoTimeCache{}
|
||||
cacheIns.Init(that.Config.GetMap("cache"), HoTimeDBInterface(&that.Db), &that.Error)
|
||||
that.HoTimeCache = &cacheIns
|
||||
//mode生产模式开启的时候才开启数据库缓存,防止调试出问题
|
||||
if that.Config.GetInt("mode") == 0 {
|
||||
that.Db.HoTimeCache = &cacheIns
|
||||
}
|
||||
}
|
||||
|
||||
//设置配置文件路径全路径或者相对路径
|
||||
func (this *Application) SetConfig(configPath ...string) {
|
||||
// SetConfig 设置配置文件路径全路径或者相对路径
|
||||
func (that *Application) SetConfig(configPath ...string) {
|
||||
|
||||
that.Log = GetLog("", true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if len(configPath) != 0 {
|
||||
this.configPath = configPath[0]
|
||||
that.configPath = configPath[0]
|
||||
}
|
||||
|
||||
if this.configPath == "" {
|
||||
this.configPath = "config/config.json"
|
||||
if that.configPath == "" {
|
||||
that.configPath = "config/config.json"
|
||||
}
|
||||
//加载配置文件
|
||||
btes, err := ioutil.ReadFile(this.configPath)
|
||||
this.Config = DeepCopyMap(Config).(Map)
|
||||
btes, err := ioutil.ReadFile(that.configPath)
|
||||
that.Config = DeepCopyMap(Config).(Map)
|
||||
if err == nil {
|
||||
|
||||
cmap := Map{}
|
||||
//文件是否损坏
|
||||
cmap.JsonToMap(string(btes), &this.Error)
|
||||
cmap.JsonToMap(string(btes), &that.Error)
|
||||
|
||||
for k, v := range cmap {
|
||||
this.Config.Put(k, v) //程序配置
|
||||
Config.Put(k, v) //系统配置
|
||||
that.Config[k] = v //程序配置
|
||||
Config[k] = v //系统配置
|
||||
}
|
||||
} else {
|
||||
log.Println("配置文件不存在,或者配置出错,使用缺省默认配置")
|
||||
that.Log.Error("配置文件不存在,或者配置出错,使用缺省默认配置")
|
||||
|
||||
}
|
||||
|
||||
if that.Error.GetError() != nil {
|
||||
fmt.Println(that.Error.GetError().Error())
|
||||
}
|
||||
|
||||
//文件如果损坏则不写入配置防止配置文件数据丢失
|
||||
if this.Error.GetError() == nil {
|
||||
var configByte bytes.Buffer
|
||||
if that.Error.GetError() == nil {
|
||||
//var configByte bytes.Buffer
|
||||
|
||||
err = json.Indent(&configByte, []byte(this.Config.ToJsonString()), "", "\t")
|
||||
//判断配置文件是否序列有变化有则修改配置,五则不变
|
||||
//判断配置文件是否序列有变化,有则修改配置,无则不变
|
||||
//fmt.Println(len(btes))
|
||||
if len(btes) != 0 && configByte.String() == string(btes) {
|
||||
configStr := that.Config.ToJsonString()
|
||||
if len(btes) != 0 && configStr == string(btes) {
|
||||
return
|
||||
}
|
||||
//写入配置说明
|
||||
var configNoteByte bytes.Buffer
|
||||
json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
|
||||
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
|
||||
//写入配置
|
||||
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
|
||||
//var configNoteByte bytes.Buffer
|
||||
configNoteStr := ConfigNote.ToJsonString()
|
||||
//_ = json.Indent(&configNoteByte, []byte(ConfigNote.ToJsonString()), "", "\t")
|
||||
|
||||
_ = os.MkdirAll(filepath.Dir(that.configPath), os.ModeDir)
|
||||
err = ioutil.WriteFile(that.configPath, []byte(configStr), os.ModePerm)
|
||||
if err != nil {
|
||||
os.MkdirAll(filepath.Dir(this.configPath), os.ModeDir)
|
||||
os.Create(this.configPath)
|
||||
err = ioutil.WriteFile(this.configPath, configByte.Bytes(), os.ModeAppend)
|
||||
if err != nil {
|
||||
this.Error.SetError(err)
|
||||
}
|
||||
//写入配置说明
|
||||
os.Create(filepath.Dir(this.configPath) + "/confignote.json")
|
||||
ioutil.WriteFile(filepath.Dir(this.configPath)+"/confignote.json", configNoteByte.Bytes(), os.ModeAppend)
|
||||
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
_ = ioutil.WriteFile(filepath.Dir(that.configPath)+"/configNote.json", []byte(configNoteStr), os.ModePerm)
|
||||
|
||||
}
|
||||
|
||||
that.Log = GetLog(that.Config.GetString("logFile"), true)
|
||||
that.Error = Error{Logger: that.Log}
|
||||
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
|
||||
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//连接判断,返回true继续传输至控制层,false则停止传输
|
||||
func (this *Application) SetConnectListener(lis func(this *Context) bool) {
|
||||
this.connectListener = append(this.connectListener, lis)
|
||||
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
|
||||
func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) {
|
||||
that.connectListener = append(that.connectListener, lis)
|
||||
}
|
||||
|
||||
//网络错误
|
||||
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
|
||||
//func (that *Application) session(w http.ResponseWriter, req *http.Request) {
|
||||
//
|
||||
//}
|
||||
|
||||
//序列化链接
|
||||
func (this *Application) urlSer(url string) (string, []string) {
|
||||
// 序列化链接
|
||||
func (that *Application) urlSer(url string) (string, []string) {
|
||||
q := strings.Index(url, "?")
|
||||
if q == -1 {
|
||||
q = len(url)
|
||||
@@ -290,9 +307,10 @@ func (this *Application) urlSer(url string) (string, []string) {
|
||||
|
||||
//访问
|
||||
|
||||
func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
nowUnixTime := time.Now()
|
||||
|
||||
_, s := this.urlSer(req.RequestURI)
|
||||
_, s := that.urlSer(req.RequestURI)
|
||||
//获取cookie
|
||||
// 如果cookie存在直接将sessionId赋值为cookie.Value
|
||||
// 如果cookie不存在就查找传入的参数中是否有token
|
||||
@@ -300,20 +318,27 @@ 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
|
||||
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
|
||||
if cookie == nil || cookie.Value != sessionId {
|
||||
needSetCookie = sessionId
|
||||
}
|
||||
//else{
|
||||
// isFirst=true;
|
||||
//}
|
||||
http.SetCookie(w, &http.Cookie{Name: this.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
||||
} else {
|
||||
} else if err == nil && cookie.Value != "" {
|
||||
sessionId = cookie.Value
|
||||
//session也没有则判断是否创建cookie
|
||||
} else {
|
||||
needSetCookie = sessionId
|
||||
}
|
||||
|
||||
unescapeUrl, err := url.QueryUnescape(req.RequestURI)
|
||||
@@ -321,71 +346,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 {
|
||||
log.Println(context.HandlerStr + time.Now().Format(" 2006-01-02 15:04 ") + Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":")))
|
||||
}
|
||||
that.crossDomain(&context, needSetCookie)
|
||||
|
||||
defer func() {
|
||||
//是否展示日志
|
||||
if that.WebConnectLog != nil {
|
||||
|
||||
//负载均衡优化
|
||||
ipStr := ""
|
||||
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)
|
||||
@@ -409,12 +440,20 @@ func (this *Application) handler(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
//设置header
|
||||
delete(header, "Content-Type")
|
||||
if this.Config.GetInt("debug") != 1 {
|
||||
if that.Config.GetInt("mode") == 0 {
|
||||
header.Set("Cache-Control", "public")
|
||||
} else {
|
||||
header.Set("Cache-Control", "no-cache")
|
||||
}
|
||||
|
||||
if strings.Index(path, ".m3u8") != -1 {
|
||||
header.Add("Content-Type", "audio/mpegurl")
|
||||
t := strings.LastIndex(path, ".")
|
||||
if t != -1 {
|
||||
tt := path[t:]
|
||||
|
||||
if MimeMaps[tt] != "" {
|
||||
|
||||
header.Add("Content-Type", MimeMaps[tt])
|
||||
}
|
||||
}
|
||||
|
||||
//w.Write(data)
|
||||
@@ -422,32 +461,68 @@ 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: "/"})
|
||||
//context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
|
||||
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,Authorization,Cookie,Set-Cookie")
|
||||
|
||||
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: "/"})
|
||||
context.Resp.Header().Set("Set-Cookie", that.Config.GetString("sessionName")+"="+sessionId+"; Path=/; SameSite=None; Secure")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
refer := context.Req.Header.Get("Referer")
|
||||
if refer != "" {
|
||||
if origin != "" {
|
||||
header.Set("Access-Control-Allow-Origin", origin)
|
||||
//return
|
||||
} else if refer != "" {
|
||||
tempInt := 0
|
||||
lastInt := strings.IndexFunc(refer, func(r rune) bool {
|
||||
if r == '/' && tempInt > 8 {
|
||||
@@ -462,5 +537,258 @@ 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,Authorization,Cookie,Set-Cookie")
|
||||
|
||||
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 {
|
||||
if appIns.Router[codeMake.GetString("name")][k2] == nil {
|
||||
appIns.Router[codeMake.GetString("name")][k2] = Ctr{}
|
||||
}
|
||||
for k3, _ := range TptProject[k2] {
|
||||
v3 := TptProject[k2][k3]
|
||||
appIns.Router[codeMake.GetString("name")][k2][k3] = v3
|
||||
}
|
||||
}
|
||||
|
||||
for k1, _ := range appIns.MakeCodeRouter[codeMake.GetString("name")].TableColumns {
|
||||
if appIns.Router[codeMake.GetString("name")][k1] == nil {
|
||||
appIns.Router[codeMake.GetString("name")][k1] = Ctr{}
|
||||
}
|
||||
|
||||
for k2, _ := range appIns.Router[codeMake.GetString("name")]["hotimeCommon"] {
|
||||
//golang毛病
|
||||
v2 := appIns.Router[codeMake.GetString("name")]["hotimeCommon"][k2]
|
||||
appIns.Router[codeMake.GetString("name")][k1][k2] = v2
|
||||
}
|
||||
}
|
||||
|
||||
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.RouterString[2] == "analyse" &&
|
||||
context.Req.Method == "GET" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"] == nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["analyse"](context)
|
||||
}
|
||||
//查询单条
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "GET" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
|
||||
return isFinished
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["info"](context)
|
||||
}
|
||||
//更新
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "PUT" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["update"] == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["update"](context)
|
||||
}
|
||||
//移除
|
||||
if len(context.RouterString) == 3 &&
|
||||
context.Req.Method == "DELETE" {
|
||||
|
||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["remove"] == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context)
|
||||
}
|
||||
|
||||
//context.View()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
+277
@@ -0,0 +1,277 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// HoTimeCache 可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
|
||||
// 缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
||||
type HoTimeCache struct {
|
||||
*Error
|
||||
dbCache *CacheDb
|
||||
redisCache *CacheRedis
|
||||
memoryCache *CacheMemory
|
||||
Config Map
|
||||
}
|
||||
|
||||
func (that *HoTimeCache) Session(key string, data ...interface{}) *Obj {
|
||||
var reData *Obj
|
||||
if len(data) == 0 {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//db缓存有
|
||||
if that.dbCache != nil && that.dbCache.SessionSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
that.redisCache.Cache(key, reData.Data)
|
||||
}
|
||||
|
||||
return reData
|
||||
}
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
//设置缓存
|
||||
//设置内存缓存
|
||||
if that.memoryCache != nil && that.memoryCache.SessionSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.SessionSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
}
|
||||
//redis缓存有
|
||||
if that.dbCache != nil && that.dbCache.SessionSet {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
return reData
|
||||
|
||||
}
|
||||
func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
||||
var reData *Obj
|
||||
if len(data) == 0 {
|
||||
//内存缓存有
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
reData = that.memoryCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil && that.redisCache.DbSet {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData.Data != nil {
|
||||
if that.memoryCache != nil && that.memoryCache.DbSet {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//db缓存有
|
||||
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...)
|
||||
}
|
||||
//db缓存有
|
||||
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 && reData.Data != nil {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//redis缓存有
|
||||
if that.redisCache != nil {
|
||||
reData = that.redisCache.Cache(key, data...)
|
||||
if reData != nil && reData.Data != nil {
|
||||
if that.memoryCache != nil {
|
||||
that.memoryCache.Cache(key, reData.Data)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//db缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
if reData != nil && 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...)
|
||||
}
|
||||
//db缓存有
|
||||
if that.dbCache != nil {
|
||||
reData = that.dbCache.Cache(key, data...)
|
||||
}
|
||||
return reData
|
||||
}
|
||||
|
||||
func (that *HoTimeCache) Init(config Map, hotimeDb HoTimeDBInterface, err ...*Error) {
|
||||
//防止空数据问题
|
||||
if config == nil {
|
||||
config = Map{}
|
||||
}
|
||||
if err[0] != nil {
|
||||
that.Error = err[0]
|
||||
}
|
||||
that.Config = config
|
||||
//memory配置初始化
|
||||
memory := that.Config.GetMap("memory")
|
||||
if memory == nil {
|
||||
memory = Map{
|
||||
"db": true,
|
||||
"session": true,
|
||||
"sort": 0,
|
||||
"timeout": 60 * 60 * 2,
|
||||
}
|
||||
|
||||
}
|
||||
if memory.Get("db") == nil {
|
||||
memory["db"] = true
|
||||
}
|
||||
|
||||
if memory.Get("session") == nil {
|
||||
memory["session"] = true
|
||||
}
|
||||
|
||||
if memory.Get("timeout") == nil {
|
||||
memory["timeout"] = 60 * 60 * 2
|
||||
}
|
||||
that.Config["memory"] = memory
|
||||
that.memoryCache = &CacheMemory{TimeOut: memory.GetCeilInt64("timeout"),
|
||||
DbSet: memory.GetBool("db"), SessionSet: memory.GetBool("session")}
|
||||
if err[0] != nil {
|
||||
that.memoryCache.SetError(err[0])
|
||||
}
|
||||
|
||||
//db配置初始化
|
||||
redis := that.Config.GetMap("redis")
|
||||
if redis != nil {
|
||||
if redis.GetString("host") == "" || redis.GetString("port") == "" {
|
||||
if err[0] != nil {
|
||||
err[0].SetError(errors.New("请检查redis配置host和port配置"))
|
||||
}
|
||||
return
|
||||
}
|
||||
if redis.Get("db") == nil {
|
||||
redis["db"] = true
|
||||
}
|
||||
|
||||
if redis.Get("session") == nil {
|
||||
redis["session"] = true
|
||||
}
|
||||
|
||||
if redis.Get("timeout") == nil {
|
||||
redis["timeout"] = 60 * 60 * 24 * 15
|
||||
}
|
||||
that.Config["redis"] = redis
|
||||
that.redisCache = &CacheRedis{TimeOut: redis.GetCeilInt64("timeout"),
|
||||
DbSet: redis.GetBool("db"), SessionSet: redis.GetBool("session"), Host: redis.GetString("host"),
|
||||
Pwd: redis.GetString("password"), Port: redis.GetCeilInt64("port")}
|
||||
|
||||
if err[0] != nil {
|
||||
that.redisCache.SetError(err[0])
|
||||
}
|
||||
}
|
||||
|
||||
//db配置初始化
|
||||
db := that.Config.GetMap("db")
|
||||
if db != nil {
|
||||
|
||||
if db.Get("db") == nil {
|
||||
db["db"] = false
|
||||
}
|
||||
|
||||
if db.Get("session") == nil {
|
||||
db["session"] = true
|
||||
}
|
||||
if db.Get("timeout") == nil {
|
||||
db["timeout"] = 60 * 60 * 24 * 30
|
||||
}
|
||||
that.Config["db"] = db
|
||||
|
||||
that.dbCache = &CacheDb{TimeOut: db.GetCeilInt64("timeout"),
|
||||
DbSet: db.GetBool("db"), SessionSet: db.GetBool("session"),
|
||||
Db: hotimeDb}
|
||||
|
||||
if err[0] != nil {
|
||||
that.dbCache.SetError(err[0])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+173
@@ -0,0 +1,173 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HoTimeDBInterface interface {
|
||||
GetPrefix() string
|
||||
Query(query string, args ...interface{}) []Map
|
||||
Exec(query string, args ...interface{}) (sql.Result, *Error)
|
||||
Get(table string, qu ...interface{}) Map
|
||||
Select(table string, qu ...interface{}) []Map
|
||||
Delete(table string, data map[string]interface{}) int64
|
||||
Update(table string, data Map, where Map) int64
|
||||
Insert(table string, data map[string]interface{}) int64
|
||||
GetType() string
|
||||
}
|
||||
|
||||
type CacheDb struct {
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
Db HoTimeDBInterface
|
||||
*Error
|
||||
ContextBase
|
||||
isInit bool
|
||||
}
|
||||
|
||||
func (that *CacheDb) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheDb) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
|
||||
func (that *CacheDb) initDbTable() {
|
||||
if that.isInit {
|
||||
return
|
||||
}
|
||||
if that.Db.GetType() == "mysql" {
|
||||
|
||||
dbNames := that.Db.Query("SELECT DATABASE()")
|
||||
|
||||
if len(dbNames) == 0 {
|
||||
return
|
||||
}
|
||||
dbName := dbNames[0].GetString("DATABASE()")
|
||||
res := that.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='" + that.Db.GetPrefix() + "cached'")
|
||||
if len(res) != 0 {
|
||||
that.isInit = true
|
||||
return
|
||||
}
|
||||
|
||||
_, e := that.Db.Exec("CREATE TABLE `" + that.Db.GetPrefix() + "cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `key` varchar(60) DEFAULT NULL, `value` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
|
||||
if e.GetError() == nil {
|
||||
that.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if that.Db.GetType() == "sqlite" {
|
||||
res := that.Db.Query(`select * from sqlite_master where type = 'table' and name = '` + that.Db.GetPrefix() + `cached'`)
|
||||
|
||||
if len(res) != 0 {
|
||||
that.isInit = true
|
||||
return
|
||||
}
|
||||
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"key" TEXT(60),
|
||||
"value" TEXT(2000),
|
||||
"time" integer,
|
||||
"endtime" integer
|
||||
);`)
|
||||
if e.GetError() == nil {
|
||||
that.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 获取Cache键只能为string类型
|
||||
func (that *CacheDb) get(key string) interface{} {
|
||||
|
||||
cached := that.Db.Get("cached", "*", Map{"key": key})
|
||||
|
||||
if cached == nil {
|
||||
return nil
|
||||
}
|
||||
//data:=cacheMap[key];
|
||||
if cached.GetInt64("endtime") <= time.Now().Unix() {
|
||||
|
||||
that.Db.Delete("cached", Map{"id": cached.GetString("id")})
|
||||
return nil
|
||||
}
|
||||
|
||||
data := Map{}
|
||||
data.JsonToMap(cached.GetString("value"))
|
||||
|
||||
return data.Get("data")
|
||||
}
|
||||
|
||||
// key value ,时间为时间戳
|
||||
func (that *CacheDb) set(key string, value interface{}, tim int64) {
|
||||
|
||||
bte, _ := json.Marshal(Map{"data": value})
|
||||
|
||||
num := that.Db.Update("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"key": key})
|
||||
if num == int64(0) {
|
||||
that.Db.Insert("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "key": key})
|
||||
}
|
||||
|
||||
//随机执行删除命令
|
||||
if Rand(1000) > 950 {
|
||||
that.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
|
||||
}
|
||||
}
|
||||
|
||||
func (that *CacheDb) delete(key string) {
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
that.Db.Delete("cached", Map{"key": key + "%"})
|
||||
|
||||
} else {
|
||||
that.Db.Delete("cached", Map{"key": key})
|
||||
}
|
||||
}
|
||||
|
||||
func (that *CacheDb) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
that.initDbTable()
|
||||
|
||||
if len(data) == 0 {
|
||||
return &Obj{Data: that.get(key)}
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
that.delete(key)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if that.TimeOut == 0 {
|
||||
//that.Time = Config.GetInt64("cacheLongTime")
|
||||
}
|
||||
tim += that.TimeOut
|
||||
}
|
||||
if len(data) == 2 {
|
||||
that.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
|
||||
if tempt > tim {
|
||||
tim = tempt
|
||||
} else if that.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
that.set(key, data[0], tim)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheMemory 基于 sync.Map 的缓存实现
|
||||
type CacheMemory struct {
|
||||
TimeOut int64
|
||||
DbSet bool
|
||||
SessionSet bool
|
||||
*Error
|
||||
cache sync.Map // 替代传统的 Map
|
||||
}
|
||||
|
||||
func (that *CacheMemory) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
}
|
||||
|
||||
func (that *CacheMemory) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
func (c *CacheMemory) get(key string) (res *Obj) {
|
||||
|
||||
res = &Obj{
|
||||
Error: *c.Error,
|
||||
}
|
||||
value, ok := c.cache.Load(key)
|
||||
if !ok {
|
||||
return res // 缓存不存在
|
||||
}
|
||||
|
||||
data := value.(cacheData)
|
||||
|
||||
// 检查是否过期
|
||||
if data.time < time.Now().Unix() {
|
||||
c.cache.Delete(key) // 删除过期缓存
|
||||
return res
|
||||
}
|
||||
res.Data = data.data
|
||||
return res
|
||||
}
|
||||
func (c *CacheMemory) set(key string, value interface{}, expireAt int64) {
|
||||
data := cacheData{
|
||||
data: value,
|
||||
time: expireAt,
|
||||
}
|
||||
c.cache.Store(key, data)
|
||||
}
|
||||
func (c *CacheMemory) delete(key string) {
|
||||
if strings.Contains(key, "*") {
|
||||
// 通配符删除
|
||||
prefix := strings.TrimSuffix(key, "*")
|
||||
c.cache.Range(func(k, v interface{}) bool {
|
||||
if strings.HasPrefix(k.(string), prefix) {
|
||||
c.cache.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
// 精确删除
|
||||
c.cache.Delete(key)
|
||||
}
|
||||
}
|
||||
func (c *CacheMemory) refreshMap() {
|
||||
go func() {
|
||||
now := time.Now().Unix()
|
||||
c.cache.Range(func(key, value interface{}) bool {
|
||||
data := value.(cacheData)
|
||||
if data.time <= now {
|
||||
c.cache.Delete(key) // 删除过期缓存
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
}
|
||||
func (c *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
now := time.Now().Unix()
|
||||
|
||||
// 随机触发刷新
|
||||
if x := RandX(1, 100000); x > 99950 {
|
||||
c.refreshMap()
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
// 读操作
|
||||
return c.get(key)
|
||||
}
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
// 删除操作
|
||||
c.delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 写操作
|
||||
expireAt := now + c.TimeOut
|
||||
if len(data) == 2 {
|
||||
if customExpire, ok := data[1].(int64); ok {
|
||||
if customExpire > now {
|
||||
expireAt = customExpire
|
||||
} else {
|
||||
expireAt = now + customExpire
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.set(key, data[0], expireAt)
|
||||
return nil
|
||||
}
|
||||
Vendored
+125
-88
@@ -1,153 +1,190 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
. "go.hoteas.com/hotime"
|
||||
"strings"
|
||||
"sync"
|
||||
"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
|
||||
pool *redis.Pool
|
||||
tag int64
|
||||
ContextBase
|
||||
*Error
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *CacheRedis) GetTag() int64 {
|
||||
func (that *CacheRedis) GetError() *Error {
|
||||
|
||||
return that.Error
|
||||
|
||||
if this.tag == int64(0) {
|
||||
this.tag = time.Now().UnixNano()
|
||||
}
|
||||
return this.tag
|
||||
}
|
||||
|
||||
func (this *CacheRedis) reCon() bool {
|
||||
var err error
|
||||
this.conn, err = redis.Dial("tcp", this.Host)
|
||||
if err != nil {
|
||||
this.conn = nil
|
||||
this.Error.SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = this.conn.Do("AUTH", this.Pwd)
|
||||
if err != nil {
|
||||
this.conn = nil
|
||||
this.Error.SetError(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
func (that *CacheRedis) SetError(err *Error) {
|
||||
that.Error = err
|
||||
}
|
||||
func (this *CacheRedis) del(key string) {
|
||||
|
||||
// 唯一标志
|
||||
func (that *CacheRedis) GetTag() int64 {
|
||||
|
||||
if that.tag == int64(0) {
|
||||
that.tag = time.Now().UnixNano()
|
||||
}
|
||||
return that.tag
|
||||
}
|
||||
|
||||
// initPool 初始化连接池(只执行一次)
|
||||
func (that *CacheRedis) initPool() {
|
||||
that.initOnce.Do(func() {
|
||||
that.pool = &redis.Pool{
|
||||
MaxIdle: 10, // 最大空闲连接数
|
||||
MaxActive: 100, // 最大活跃连接数,0表示无限制
|
||||
IdleTimeout: 5 * time.Minute, // 空闲连接超时时间
|
||||
Wait: true, // 当连接池耗尽时是否等待
|
||||
Dial: func() (redis.Conn, error) {
|
||||
conn, err := redis.Dial("tcp", that.Host+":"+ObjToStr(that.Port),
|
||||
redis.DialConnectTimeout(5*time.Second),
|
||||
redis.DialReadTimeout(3*time.Second),
|
||||
redis.DialWriteTimeout(3*time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if that.Pwd != "" {
|
||||
if _, err := conn.Do("AUTH", that.Pwd); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
if time.Since(t) < time.Minute {
|
||||
return nil
|
||||
}
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// getConn 从连接池获取连接
|
||||
func (that *CacheRedis) getConn() redis.Conn {
|
||||
that.initPool()
|
||||
if that.pool == nil {
|
||||
return nil
|
||||
}
|
||||
return that.pool.Get()
|
||||
}
|
||||
|
||||
func (that *CacheRedis) del(key string) {
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
if del != -1 {
|
||||
val, err := redis.Strings(this.conn.Do("KEYS", key))
|
||||
val, err := redis.Strings(conn.Do("KEYS", key))
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
return
|
||||
}
|
||||
this.conn.Send("MULTI")
|
||||
for i, _ := range val {
|
||||
this.conn.Send("DEL", val[i])
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
conn.Send("MULTI")
|
||||
for i := range val {
|
||||
conn.Send("DEL", val[i])
|
||||
}
|
||||
_, err = conn.Do("EXEC")
|
||||
if err != nil {
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
this.conn.Do("EXEC")
|
||||
} else {
|
||||
_, err := this.conn.Do("DEL", key)
|
||||
_, err := conn.Do("DEL", key)
|
||||
if err != nil {
|
||||
this.Error.SetError(err)
|
||||
_, err = this.conn.Do("PING")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
_, err = this.conn.Do("DEL", key)
|
||||
}
|
||||
}
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheRedis) set(key string, value string, time int64) {
|
||||
_, err := this.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
// key value ,时间为时间戳
|
||||
func (that *CacheRedis) set(key string, value string, expireSeconds int64) {
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err := conn.Do("SET", key, value, "EX", ObjToStr(expireSeconds))
|
||||
if err != nil {
|
||||
|
||||
this.Error.SetError(err)
|
||||
_, err = this.conn.Do("PING")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
_, err = this.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||
}
|
||||
}
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheRedis) get(key string) *Obj {
|
||||
func (that *CacheRedis) get(key string) *Obj {
|
||||
reData := &Obj{}
|
||||
conn := that.getConn()
|
||||
if conn == nil {
|
||||
return reData
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var err error
|
||||
reData.Data, err = redis.String(this.conn.Do("GET", key))
|
||||
reData.Data, err = redis.String(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")
|
||||
if err != nil {
|
||||
if this.reCon() {
|
||||
reData.Data, err = redis.String(this.conn.Do("GET", key))
|
||||
}
|
||||
}
|
||||
|
||||
return reData
|
||||
that.Error.SetError(err)
|
||||
}
|
||||
}
|
||||
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 !re {
|
||||
return reData
|
||||
}
|
||||
}
|
||||
|
||||
//查询缓存
|
||||
if len(data) == 0 {
|
||||
|
||||
reData = this.get(key)
|
||||
reData = that.get(key)
|
||||
return reData
|
||||
|
||||
}
|
||||
|
||||
tim := int64(0)
|
||||
//删除缓存
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.del(key)
|
||||
that.del(key)
|
||||
return reData
|
||||
}
|
||||
//添加缓存
|
||||
if len(data) == 1 {
|
||||
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheShortTime")
|
||||
if that.TimeOut == 0 {
|
||||
//that.Time = Config.GetInt64("cacheShortTime")
|
||||
}
|
||||
|
||||
tim += this.Time
|
||||
tim += that.TimeOut
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
that.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], that.Error)
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
|
||||
} else if that.GetError() == nil {
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
|
||||
this.set(key, ObjToStr(data[0]), tim)
|
||||
that.set(key, ObjToStr(data[0]), tim)
|
||||
|
||||
return reData
|
||||
|
||||
}
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
type CacheIns interface {
|
||||
//set(key string, value interface{}, time int64)
|
||||
//get(key string) interface{}
|
||||
//delete(key string)
|
||||
GetError() *Error
|
||||
SetError(err *Error)
|
||||
Cache(key string, data ...interface{}) *Obj
|
||||
}
|
||||
|
||||
// 单条缓存数据
|
||||
type cacheData struct {
|
||||
time int64
|
||||
data interface{}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheDb struct {
|
||||
Time int64
|
||||
Db *HoTimeDB
|
||||
contextBase
|
||||
isInit bool
|
||||
}
|
||||
|
||||
func (this *CacheDb) initDbTable() {
|
||||
if this.isInit {
|
||||
return
|
||||
}
|
||||
if this.Db.Type == "mysql" {
|
||||
|
||||
dbNames := this.Db.Query("SELECT DATABASE()")
|
||||
|
||||
if len(dbNames) == 0 {
|
||||
return
|
||||
}
|
||||
dbName := dbNames[0].GetString("DATABASE()")
|
||||
res := this.Db.Query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" + dbName + "' AND TABLE_NAME='cached'")
|
||||
if len(res) != 0 {
|
||||
this.isInit = true
|
||||
return
|
||||
}
|
||||
|
||||
_, e := this.Db.Exec("CREATE TABLE `cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
|
||||
if e.GetError() == nil {
|
||||
this.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if this.Db.Type == "sqlite" {
|
||||
res := this.Db.Query(`select * from sqlite_master where type = 'table' and name = 'cached'`)
|
||||
|
||||
if len(res) != 0 {
|
||||
this.isInit = true
|
||||
return
|
||||
}
|
||||
_, e := this.Db.Exec(`CREATE TABLE "cached" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"ckey" TEXT(60),
|
||||
"cvalue" TEXT(2000),
|
||||
"time" integer,
|
||||
"endtime" integer
|
||||
);`)
|
||||
if e.GetError() == nil {
|
||||
this.isInit = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (this *CacheDb) get(key string) interface{} {
|
||||
|
||||
cached := this.Db.Get("cached", "*", Map{"ckey": key})
|
||||
|
||||
if cached == nil {
|
||||
return nil
|
||||
}
|
||||
//data:=cacheMap[key];
|
||||
if cached.GetInt64("endtime") <= time.Now().Unix() {
|
||||
|
||||
this.Db.Delete("cached", Map{"id": cached.GetString("id")})
|
||||
return nil
|
||||
}
|
||||
|
||||
data := Map{}
|
||||
data.JsonToMap(cached.GetString("cvalue"))
|
||||
|
||||
return data.Get("data")
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheDb) set(key string, value interface{}, tim int64) {
|
||||
|
||||
bte, _ := json.Marshal(Map{"data": value})
|
||||
|
||||
num := this.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
|
||||
if num == int64(0) {
|
||||
this.Db.Insert("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
|
||||
}
|
||||
|
||||
//随机执行删除命令
|
||||
if Rand(1000) > 950 {
|
||||
this.Db.Delete("cached", Map{"endtime[<]": time.Now().Unix()})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheDb) delete(key string) {
|
||||
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
this.Db.Delete("cached", Map{"ckey": key + "%"})
|
||||
|
||||
} else {
|
||||
this.Db.Delete("cached", Map{"ckey": key})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CacheDb) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
this.initDbTable()
|
||||
|
||||
if len(data) == 0 {
|
||||
return &Obj{Data: this.get(key)}
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.delete(key)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheLongTime")
|
||||
}
|
||||
tim += this.Time
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
|
||||
if tempt > tim {
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
this.set(key, data[0], tim)
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheMemory struct {
|
||||
Time int64
|
||||
Map
|
||||
contextBase
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
//获取Cache键只能为string类型
|
||||
func (this *CacheMemory) get(key string) interface{} {
|
||||
this.Error.SetError(nil)
|
||||
if this.Map == nil {
|
||||
this.Map = Map{}
|
||||
}
|
||||
|
||||
if this.Map[key] == nil {
|
||||
return nil
|
||||
}
|
||||
data := this.Map.Get(key, &this.Error).(cacheData)
|
||||
if this.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if data.time < time.Now().Unix() {
|
||||
delete(this.Map, key)
|
||||
return nil
|
||||
}
|
||||
return data.data
|
||||
}
|
||||
|
||||
func (this *CacheMemory) refreshMap() {
|
||||
|
||||
go func() {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
for key, v := range this.Map {
|
||||
data := v.(cacheData)
|
||||
if data.time <= time.Now().Unix() {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
//key value ,时间为时间戳
|
||||
func (this *CacheMemory) set(key string, value interface{}, time int64) {
|
||||
this.Error.SetError(nil)
|
||||
var data cacheData
|
||||
|
||||
if this.Map == nil {
|
||||
this.Map = Map{}
|
||||
}
|
||||
|
||||
dd := this.Map[key]
|
||||
|
||||
if dd == nil {
|
||||
data = cacheData{}
|
||||
} else {
|
||||
data = dd.(cacheData)
|
||||
}
|
||||
|
||||
data.time = time
|
||||
data.data = value
|
||||
|
||||
this.Map.Put(key, data)
|
||||
}
|
||||
|
||||
func (this *CacheMemory) delete(key string) {
|
||||
del := strings.Index(key, "*")
|
||||
//如果通配删除
|
||||
if del != -1 {
|
||||
key = Substr(key, 0, del)
|
||||
for k, _ := range this.Map {
|
||||
if strings.Index(k, key) != -1 {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
delete(this.Map, key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (this *CacheMemory) Cache(key string, data ...interface{}) *Obj {
|
||||
|
||||
x := RandX(1, 100000)
|
||||
if x > 99950 {
|
||||
this.refreshMap()
|
||||
}
|
||||
if this.mutex == nil {
|
||||
this.mutex = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
reData := &Obj{Data: nil}
|
||||
|
||||
if len(data) == 0 {
|
||||
this.mutex.RLock()
|
||||
reData.Data = this.get(key)
|
||||
this.mutex.RUnlock()
|
||||
return reData
|
||||
}
|
||||
tim := time.Now().Unix()
|
||||
|
||||
if len(data) == 1 && data[0] == nil {
|
||||
this.mutex.Lock()
|
||||
this.delete(key)
|
||||
this.mutex.Unlock()
|
||||
return reData
|
||||
}
|
||||
|
||||
if len(data) == 1 {
|
||||
if this.Time == 0 {
|
||||
this.Time = Config.GetInt64("cacheShortTime")
|
||||
}
|
||||
|
||||
tim = tim + this.Time
|
||||
|
||||
}
|
||||
if len(data) == 2 {
|
||||
this.Error.SetError(nil)
|
||||
tempt := ObjToInt64(data[1], &this.Error)
|
||||
|
||||
if tempt > tim {
|
||||
|
||||
tim = tempt
|
||||
} else if this.GetError() == nil {
|
||||
|
||||
tim = tim + tempt
|
||||
}
|
||||
}
|
||||
this.mutex.Lock()
|
||||
this.set(key, data[0], tim)
|
||||
this.mutex.Unlock()
|
||||
return reData
|
||||
|
||||
}
|
||||
+188
@@ -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},
|
||||
//}
|
||||
+1330
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,236 @@
|
||||
package code
|
||||
|
||||
var InitTpt = `package {{name}}
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
var ID = "{{id}}"
|
||||
|
||||
// Project 管理端项目
|
||||
var Project = Proj{
|
||||
//"user": UserCtr,
|
||||
{{tablesCtr}}
|
||||
"hotime":Ctr{
|
||||
"login": func(that *Context) {
|
||||
|
||||
name := that.Req.FormValue("name")
|
||||
password := that.Req.FormValue("password")
|
||||
if name == "" || password == "" {
|
||||
that.Display(3, "参数不足")
|
||||
return
|
||||
}
|
||||
user := that.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
|
||||
if user == nil {
|
||||
that.Display(5, "登录失败")
|
||||
return
|
||||
}
|
||||
that.Session("admin_id", user.GetCeilInt("id"))
|
||||
that.Session("admin_name", name)
|
||||
that.Display(0, that.SessionId)
|
||||
},
|
||||
"logout": func(that *Context) {
|
||||
that.Session("admin_id", nil)
|
||||
that.Session("admin_name", nil)
|
||||
that.Display(0, "退出登录成功")
|
||||
},
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info("admin", data, that.Db)
|
||||
where := Map{"id": that.Session("admin_id").ToCeilInt()}
|
||||
if len(inData) ==1 {
|
||||
inData["id"] =where["id"]
|
||||
where = Map{"AND": inData}
|
||||
}else if len(inData) >1 {
|
||||
where["OR"]=inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
re := that.Db.Get("admin", str, where)
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns["admin"][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
re[column.GetString("link")] = that.Db.Get(column.GetString("link"),"id," +column.GetString("value"), Map{"id": v})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
},
|
||||
}
|
||||
`
|
||||
var CtrTpt = `package {{name}}
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var {{table}}Ctr = Ctr{
|
||||
"info": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
str, inData := that.MakeCodeRouter[hotimeName].Info(that.RouterString[1], data, that.Db)
|
||||
where := Map{"id": that.RouterString[2]}
|
||||
|
||||
if len(inData) ==1 {
|
||||
inData["id"] =where["id"]
|
||||
where = Map{"AND": inData}
|
||||
}else if len(inData) >1 {
|
||||
where["OR"]=inData
|
||||
where = Map{"AND": where}
|
||||
}
|
||||
|
||||
re := that.Db.Get(that.RouterString[1], str, where)
|
||||
|
||||
if re == nil {
|
||||
that.Display(4, "找不到对应信息")
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range re {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
if (column["list"] == nil || column.GetBool("list")) && column.GetString("link") != "" {
|
||||
re[column.GetString("link")] = that.Db.Get(column.GetString("link"), "id,"+column.GetString("value"), Map{"id": v})
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"add": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Add(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
re := that.Db.Insert(that.RouterString[1], inData)
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "无法插入对应数据")
|
||||
return
|
||||
}
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||
index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
|
||||
inData["index"] = index.GetString("index")+ObjToStr(re)+","
|
||||
that.Db.Update(that.RouterString[1],Map{"index":inData["index"]},Map{"id":re})
|
||||
}else if inData.GetString("index") != ""{
|
||||
inData["index"] = "," + ObjToStr(re) + ","
|
||||
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"update": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Edit(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "没有找到要更新的数据")
|
||||
return
|
||||
}
|
||||
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||
Index := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": that.RouterString[2]})
|
||||
parentIndex := that.Db.Get(that.RouterString[1], "` + "`index`" + `", Map{"id": inData.Get("parent_id")})
|
||||
inData["index"] = parentIndex.GetString("index") + that.RouterString[2] + ","
|
||||
|
||||
childNodes := that.Db.Select(that.RouterString[1], "id,` + "`index`" + `", Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
|
||||
for _, v := range childNodes {
|
||||
v["index"] = strings.Replace(v.GetString("index"), Index.GetString("index"), inData.GetString("index"), -1)
|
||||
that.Db.Update(that.RouterString[1], Map{"index": v["index"]}, Map{"id": v.GetCeilInt("id")})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
re := that.Db.Update(that.RouterString[1], inData, Map{"id": that.RouterString[2]})
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "更新数据失败")
|
||||
return
|
||||
}
|
||||
|
||||
that.Display(0, re)
|
||||
},
|
||||
"remove": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
inData := that.MakeCodeRouter[hotimeName].Delete(that.RouterString[1], that.Req)
|
||||
if inData == nil {
|
||||
that.Display(3, "请求参数不足")
|
||||
return
|
||||
}
|
||||
re :=int64(0)
|
||||
//索引管理,便于检索以及权限
|
||||
if inData.Get("parent_id") != nil && inData.GetSlice("index") != nil {
|
||||
re=that.Db.Delete(that.RouterString[1], Map{"index[~]": "," + that.RouterString[2] + ","})
|
||||
}else {
|
||||
re=that.Db.Delete(that.RouterString[1], Map{"id": that.RouterString[2]})
|
||||
}
|
||||
|
||||
if re == 0 {
|
||||
that.Display(4, "删除数据失败")
|
||||
return
|
||||
}
|
||||
that.Display(0, "删除成功")
|
||||
},
|
||||
|
||||
"search": func(that *Context) {
|
||||
hotimeName := that.RouterString[0]
|
||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||
|
||||
columnStr, leftJoin, where := that.MakeCodeRouter[hotimeName].Search(that.RouterString[1], data, that.Req, that.Db)
|
||||
|
||||
page := ObjToInt(that.Req.FormValue("page"))
|
||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
count := that.Db.Count(that.RouterString[1], leftJoin, where)
|
||||
reData := that.Db.Page(page, pageSize).
|
||||
PageSelect(that.RouterString[1], leftJoin, columnStr, where)
|
||||
|
||||
for _, v := range reData {
|
||||
for k, _ := range v {
|
||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
||||
if column == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if column["list"] != false && column["name"] == "parent_id" && column.GetString("link") != "" {
|
||||
parentC := that.Db.Get(column.GetString("link"), column.GetString("value"), Map{"id": v.GetCeilInt(k)})
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = ""
|
||||
if parentC != nil {
|
||||
v[column.GetString("link")+"_"+column.GetString("name")+"_"+column.GetString("value")] = parentC.GetString(column.GetString("value"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
that.Display(0, Map{"count": count, "data": reData})
|
||||
},
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContextBase struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
// 唯一标志
|
||||
func (that *ContextBase) GetTag() string {
|
||||
|
||||
if that.tag == "" {
|
||||
that.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
|
||||
}
|
||||
return that.tag
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Error 框架层处理错误
|
||||
type Error struct {
|
||||
Logger *logrus.Logger
|
||||
error
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (that *Error) GetError() error {
|
||||
that.mu.RLock()
|
||||
defer that.mu.RUnlock()
|
||||
return that.error
|
||||
}
|
||||
|
||||
func (that *Error) SetError(err error) {
|
||||
that.mu.Lock()
|
||||
that.error = err
|
||||
that.mu.Unlock()
|
||||
|
||||
if that.Logger != nil && err != nil {
|
||||
that.Logger.Warn(err)
|
||||
}
|
||||
}
|
||||
+83
-10
@@ -1,10 +1,11 @@
|
||||
package hotime
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//安全锁
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
// return res
|
||||
//}
|
||||
|
||||
//字符串首字符大写
|
||||
// StrFirstToUpper 字符串首字符大写
|
||||
func StrFirstToUpper(str string) string {
|
||||
if len(str) == 0 {
|
||||
return str
|
||||
@@ -36,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)
|
||||
@@ -67,8 +140,8 @@ func Substr(str string, start int, length int) string {
|
||||
return string(rs[start:end])
|
||||
}
|
||||
|
||||
//获取最后出现字符串的下标
|
||||
//return 找不到返回 -1
|
||||
// IndexLastStr 获取最后出现字符串的下标
|
||||
// return 找不到返回 -1
|
||||
func IndexLastStr(str, sep string) int {
|
||||
sepSlice := []rune(sep)
|
||||
strSlice := []rune(str)
|
||||
@@ -98,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))
|
||||
@@ -106,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++ {
|
||||
@@ -131,7 +204,7 @@ func Random() float64 {
|
||||
|
||||
}
|
||||
|
||||
//随机数范围
|
||||
// RandX 随机数范围
|
||||
func RandX(small int, max int) int {
|
||||
res := 0
|
||||
//随机对象
|
||||
@@ -183,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)
|
||||
@@ -242,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
@@ -0,0 +1,189 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// hotime的常用map
|
||||
type Map map[string]interface{}
|
||||
|
||||
// 获取string
|
||||
func (that Map) GetString(key string, err ...*Error) string {
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((that)[key])
|
||||
|
||||
}
|
||||
|
||||
func (that *Map) Pointer() *Map {
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// 增加接口
|
||||
func (that Map) Put(key string, value interface{}) {
|
||||
//if that==nil{
|
||||
// that=Map{}
|
||||
//}
|
||||
that[key] = value
|
||||
}
|
||||
|
||||
// 删除接口
|
||||
func (that Map) Delete(key string) {
|
||||
delete(that, key)
|
||||
|
||||
}
|
||||
|
||||
// 获取Int
|
||||
func (that Map) GetInt(key string, err ...*Error) int {
|
||||
v := ObjToInt((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取Int
|
||||
func (that Map) GetInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取向上取整Int64
|
||||
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取向上取整Int
|
||||
func (that Map) GetCeilInt(key string, err ...*Error) int {
|
||||
v := ObjToCeilInt((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取向上取整float64
|
||||
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取Float64
|
||||
func (that Map) GetFloat64(key string, err ...*Error) float64 {
|
||||
|
||||
v := ObjToFloat64((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetSlice(key string, err ...*Error) Slice {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToSlice((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
func (that Map) GetBool(key string, err ...*Error) bool {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToBool((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetTime(key string, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) RangeSort(callback func(k string, v interface{}) (isEnd bool)) {
|
||||
testQu := []string{}
|
||||
//testQuData:= qu[0].(Map)
|
||||
for key, _ := range that {
|
||||
//fmt.Println(key, ":", value)
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
for _, k := range testQu {
|
||||
re := callback(k, that[k])
|
||||
if re {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that Map) GetMap(key string, err ...*Error) Map {
|
||||
//var data Slice
|
||||
|
||||
v := ObjToMap((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Map) Get(key string, err ...*Error) interface{} {
|
||||
|
||||
if v, ok := (that)[key]; ok {
|
||||
return v
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 请传递指针过来
|
||||
func (that Map) ToStruct(stct interface{}) {
|
||||
|
||||
data := reflect.ValueOf(stct).Elem()
|
||||
for k, v := range that {
|
||||
ks := StrFirstToUpper(k)
|
||||
dkey := data.FieldByName(ks)
|
||||
if !dkey.IsValid() {
|
||||
continue
|
||||
}
|
||||
switch dkey.Type().String() {
|
||||
case "int":
|
||||
dkey.SetInt(that.GetInt64(k))
|
||||
case "int64":
|
||||
dkey.Set(reflect.ValueOf(that.GetInt64(k)))
|
||||
case "float64":
|
||||
dkey.Set(reflect.ValueOf(that.GetFloat64(k)))
|
||||
case "string":
|
||||
dkey.Set(reflect.ValueOf(that.GetString(k)))
|
||||
case "interface{}":
|
||||
dkey.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (that Map) ToJsonString() string {
|
||||
return ObjToStr(that)
|
||||
|
||||
}
|
||||
|
||||
func (that Map) JsonToMap(jsonStr string, err ...*Error) {
|
||||
e := json.Unmarshal([]byte(jsonStr), &that)
|
||||
if e != nil && len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
// 对象封装方便取用
|
||||
type Obj struct {
|
||||
Data interface{}
|
||||
Error
|
||||
ContextBase
|
||||
}
|
||||
|
||||
func (that *Obj) Put(data interface{}) {
|
||||
that.Data = data
|
||||
}
|
||||
|
||||
func (that *Obj) ToInt(err ...Error) int {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToInt(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToTime(err ...Error) *time.Time {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToTime(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToInt64(err ...Error) int64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToInt64(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToFloat64(err ...Error) float64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToFloat64(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
// ToCeilFloat64 获取向上取整float64
|
||||
func (that *Obj) ToCeilFloat64(err ...*Error) float64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilFloat64(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that *Obj) ToStr() string {
|
||||
|
||||
return ObjToStr(that.Data)
|
||||
}
|
||||
|
||||
func (that *Obj) ToMap(err ...Error) Map {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToMap(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToSlice(err ...Error) Slice {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToSlice(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToMapArray(err ...Error) []Map {
|
||||
if len(err) != 0 {
|
||||
that.Error = err[0]
|
||||
}
|
||||
return ObjToMapArray(that.Data, &that.Error)
|
||||
}
|
||||
|
||||
func (that *Obj) ToObj() interface{} {
|
||||
|
||||
return that.Data
|
||||
}
|
||||
|
||||
// 获取向上取整Int64
|
||||
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt64(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// 获取向上取整Int
|
||||
func (that *Obj) ToCeilInt(err ...*Error) int {
|
||||
if len(err) != 0 {
|
||||
that.Error = *err[0]
|
||||
}
|
||||
v := ObjToCeilInt(that.Data, err...)
|
||||
return v
|
||||
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package hotime
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//仅限于hotime.Slice
|
||||
// 仅限于hotime.Slice
|
||||
func ObjToMap(obj interface{}, e ...*Error) Map {
|
||||
var err error
|
||||
var v Map
|
||||
@@ -58,7 +60,7 @@ func ObjToMapArray(obj interface{}, e ...*Error) []Map {
|
||||
return res
|
||||
}
|
||||
|
||||
//仅限于hotime.Slice
|
||||
// 仅限于hotime.Slice
|
||||
func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
||||
var err error
|
||||
var v Slice
|
||||
@@ -96,6 +98,87 @@ 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)
|
||||
timeNewStr := ""
|
||||
timeNewStrs := strings.Split(tStr, "-")
|
||||
for _, v := range timeNewStrs {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if len(v) == 1 {
|
||||
v = "0" + v
|
||||
}
|
||||
if timeNewStr == "" {
|
||||
timeNewStr = v
|
||||
continue
|
||||
}
|
||||
timeNewStr = timeNewStr + "-" + v
|
||||
}
|
||||
tStr = timeNewStr
|
||||
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))
|
||||
t := time.UnixMicro(tInt / 1000)
|
||||
return &t
|
||||
//微秒级别
|
||||
} else if len(ObjToStr(tInt)) > 13 {
|
||||
//t := time.Time{}.Add(time.Microsecond * time.Duration(tInt))
|
||||
t := time.UnixMicro(tInt)
|
||||
return &t
|
||||
//毫秒级别
|
||||
} else if len(ObjToStr(tInt)) > 10 {
|
||||
//t := time.Time{}.Add(time.Millisecond * time.Duration(tInt))
|
||||
t := time.UnixMilli(tInt)
|
||||
return &t
|
||||
//秒级别
|
||||
} else if len(ObjToStr(tInt)) > 9 {
|
||||
//t := time.Time{}.Add(time.Second * time.Duration(tInt))
|
||||
t := time.Unix(tInt, 0)
|
||||
return &t
|
||||
} else if len(ObjToStr(tInt)) > 3 {
|
||||
t, e := time.Parse("2006", ObjToStr(tInt))
|
||||
if e == nil {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
var err error
|
||||
v := float64(0)
|
||||
@@ -135,6 +218,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)
|
||||
}
|
||||
@@ -142,21 +234,21 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||
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)
|
||||
@@ -212,6 +304,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 +349,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)
|
||||
}
|
||||
@@ -242,7 +360,7 @@ func ObjToStr(obj interface{}) string {
|
||||
return str
|
||||
}
|
||||
|
||||
//转换为Map
|
||||
// 转换为Map
|
||||
func StrToMap(string string) Map {
|
||||
data := Map{}
|
||||
data.JsonToMap(string)
|
||||
@@ -250,7 +368,7 @@ func StrToMap(string string) Map {
|
||||
return data
|
||||
}
|
||||
|
||||
//转换为Slice
|
||||
// 转换为Slice
|
||||
func StrToSlice(string string) Slice {
|
||||
|
||||
data := ObjToSlice(string)
|
||||
@@ -258,7 +376,7 @@ func StrToSlice(string string) Slice {
|
||||
return data
|
||||
}
|
||||
|
||||
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
func StrArrayToJsonStr(a string) string {
|
||||
|
||||
if len(a) > 2 {
|
||||
@@ -276,7 +394,7 @@ func StrArrayToJsonStr(a string) string {
|
||||
return a
|
||||
}
|
||||
|
||||
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||
func JsonStrToStrArray(a string) string {
|
||||
//a = strings.Replace(a, `"`, "", -1)
|
||||
if len(a) != 0 {
|
||||
@@ -286,7 +404,7 @@ func JsonStrToStrArray(a string) string {
|
||||
return "," + a + ","
|
||||
}
|
||||
|
||||
//字符串转int
|
||||
// 字符串转int
|
||||
func StrToInt(s string) (int, error) {
|
||||
i, err := strconv.Atoi(s)
|
||||
return i, err
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Slice []interface{}
|
||||
|
||||
// GetString 获取string
|
||||
func (that Slice) GetString(key int, err ...*Error) string {
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(nil)
|
||||
}
|
||||
return ObjToStr((that)[key])
|
||||
}
|
||||
|
||||
func (that Slice) GetTime(key int, err ...*Error) *time.Time {
|
||||
|
||||
v := ObjToTime((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetInt 获取Int
|
||||
func (that Slice) GetInt(key int, err ...*Error) int {
|
||||
v := ObjToInt((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetInt64 获取Int
|
||||
func (that Slice) GetInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToInt64((that)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// GetCeilInt64 获取向上取整Int64
|
||||
func (that Slice) GetCeilInt64(key int, err ...*Error) int64 {
|
||||
v := ObjToCeilInt64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetCeilInt 获取向上取整Int
|
||||
func (that Slice) GetCeilInt(key int, err ...*Error) int {
|
||||
v := ObjToCeilInt((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetCeilFloat64 获取向上取整float64
|
||||
func (that Slice) GetCeilFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToCeilFloat64((that)[key], err...)
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
// GetFloat64 获取Float64
|
||||
func (that Slice) GetFloat64(key int, err ...*Error) float64 {
|
||||
v := ObjToFloat64((that)[key], err...)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) GetSlice(key int, err ...*Error) Slice {
|
||||
v := ObjToSlice((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) GetBool(key int, err ...*Error) bool {
|
||||
|
||||
//var v Slice
|
||||
v := ObjToBool((that)[key], err...)
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (that Slice) GetMap(key int, err ...*Error) Map {
|
||||
//var v Map
|
||||
v := ObjToMap((that)[key], err...)
|
||||
return v
|
||||
}
|
||||
|
||||
func (that Slice) Get(key int, err ...*Error) interface{} {
|
||||
|
||||
if key < len(that) {
|
||||
return that[key]
|
||||
}
|
||||
e := errors.New("没有存储key及对应的数据")
|
||||
if len(err) != 0 {
|
||||
err[0].SetError(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (that Slice) Put(key int, value interface{}) {
|
||||
that[key] = value
|
||||
}
|
||||
|
||||
func (that Slice) ToJsonString() string {
|
||||
return ObjToStr(that)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"flow": {},
|
||||
"id": "d751713988987e9331980363e24189ce",
|
||||
"label": "HoTime管理平台",
|
||||
"labelConfig": {
|
||||
"add": "添加",
|
||||
"delete": "删除",
|
||||
"download": "下载清单",
|
||||
"edit": "编辑",
|
||||
"info": "查看详情",
|
||||
"show": "开启"
|
||||
},
|
||||
"menus": [],
|
||||
"name": "admin",
|
||||
"stop": [
|
||||
"role",
|
||||
"org"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"cache": {
|
||||
"memory": {
|
||||
"db": true,
|
||||
"session": true,
|
||||
"timeout": 7200
|
||||
}
|
||||
},
|
||||
"codeConfig": [
|
||||
{
|
||||
"config": "config/app.json",
|
||||
"mode": 0,
|
||||
"name": "",
|
||||
"rule": "config/rule.json",
|
||||
"table": "admin"
|
||||
}
|
||||
],
|
||||
"db": {
|
||||
"sqlite": {
|
||||
"path": "config/data.db"
|
||||
}
|
||||
},
|
||||
"defFile": [
|
||||
"index.html",
|
||||
"index.htm"
|
||||
],
|
||||
"error": {
|
||||
"1": "内部系统异常",
|
||||
"2": "访问权限异常",
|
||||
"3": "请求参数异常",
|
||||
"4": "数据处理异常",
|
||||
"5": "数据结果异常"
|
||||
},
|
||||
"mode": 2,
|
||||
"port": "80",
|
||||
"sessionName": "HOTIME",
|
||||
"tpt": "tpt"
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"cache": {
|
||||
"db": {
|
||||
"db": "默认false,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 30,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"memory": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 2,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"redis": {
|
||||
"db": "默认true,非必须,缓存数据库,启用后能减少数据库的读写压力",
|
||||
"host": "默认服务ip:127.0.0.1,必须,如果需要使用redis服务时配置,",
|
||||
"password": "默认密码空,必须,如果需要使用redis服务时配置,默认密码空",
|
||||
"port": "默认服务端口:6379,必须,如果需要使用redis服务时配置,",
|
||||
"session": "默认true,非必须,缓存web session,同时缓存session保持的用户缓存",
|
||||
"timeout": "默认60 * 60 * 24 * 15,非必须,过期时间,超时自动删除"
|
||||
},
|
||||
"注释": "可配置memory,db,redis,默认启用memory,默认优先级为memory\u003eredis\u003edb,memory与数据库缓存设置项一致,缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用"
|
||||
},
|
||||
"codeConfig": [
|
||||
"注释:配置即启用,非必须,默认无",
|
||||
{
|
||||
"config": "默认config/app.json,必须,接口描述配置文件",
|
||||
"configDB": "默认无,非必须,有则每次将数据库数据生成到此目录用于配置读写,无则不生成",
|
||||
"mode": "默认0,非必须,0为内嵌代码模式,1为生成代码模式",
|
||||
"name": "默认无,非必须,有则生成代码到此目录,无则采用缺省模式使用表名,如设置为:admin,将在admin目录生成包名为admin的代码",
|
||||
"rule": "默认config/rule.json,非必须,有则按改规则生成接口,无则按系统内嵌方式生成",
|
||||
"table": "默认admin,必须,根据数据库内当前表名做为用户生成数据"
|
||||
}
|
||||
],
|
||||
"crossDomain": "默认空 非必须,空字符串为不开启,如果需要跨域设置,auto为智能开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
|
||||
"db": {
|
||||
"mysql": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"prefix": "默认空,非必须,数据表前缀",
|
||||
"slave": {
|
||||
"host": "默认127.0.0.1,必须,数据库ip地址",
|
||||
"name": "默认test,必须,数据库名称",
|
||||
"password": "默认root,必须,数据库密码",
|
||||
"port": "默认3306,必须,数据库端口",
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "从数据库配置,mysql里配置slave项即启用主从读写,减少数据库压力"
|
||||
},
|
||||
"user": "默认root,必须,数据库用户名",
|
||||
"注释": "除prefix及主从数据库slave项,其他全部必须"
|
||||
},
|
||||
"sqlite": {
|
||||
"path": "默认config/data.db,必须,数据库位置"
|
||||
},
|
||||
"注释": "配置即启用,非必须,默认使用sqlite数据库"
|
||||
},
|
||||
"defFile": "默认访问index.html或者index.htm文件,必须,默认访问文件类型",
|
||||
"error": {
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
|
||||
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始"
|
||||
},
|
||||
"logFile": "无默认,非必须,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"logLevel": "默认0,必须,0关闭,1打印,日志等级",
|
||||
"mode": "默认0,非必须,0生产模式,1,测试模式,2开发模式,3内嵌代码模式,在开发模式下会显示更多的数据用于开发测试,并能够辅助研发,自动生成配置文件、代码等功能,web无缓存,数据库不启用缓存",
|
||||
"modeRouterStrict": "默认false,必须,路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
|
||||
"port": "默认80,必须,web服务开启Http端口,0为不启用http服务,默认80",
|
||||
"sessionName": "默认HOTIME,必须,设置session的cookie名",
|
||||
"tlsCert": "默认空,非必须,https证书",
|
||||
"tlsKey": "默认空,非必须,https密钥",
|
||||
"tlsPort": "默认空,非必须,web服务https端口,0为不启用https服务",
|
||||
"tpt": "默认tpt,必须,web静态文件目录,默认为程序目录下tpt目录",
|
||||
"webConnectLogFile": "无默认,非必须,webConnectLogShow开启之后才能使用,如果需要存储日志文件时使用,保存格式为:a/b/c/20060102150405.txt,将生成:a/b/c/年月日时分秒.txt,按需设置",
|
||||
"webConnectLogShow": "默认true,非必须,访问日志如果需要web访问链接、访问ip、访问时间打印,false为关闭true开启此功能"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -1,10 +1,4 @@
|
||||
package hotime
|
||||
|
||||
const (
|
||||
LOG_FMT = 0
|
||||
LOG_NIL = 1
|
||||
LOG_FILE = 2
|
||||
)
|
||||
|
||||
//session储存头
|
||||
// HEAD_SESSION_ADD session储存头
|
||||
const HEAD_SESSION_ADD = "session#"
|
||||
|
||||
+161
-21
@@ -1,65 +1,205 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
. "code.hoteas.com/golang/hotime/db"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
// 请求参数缓存
|
||||
reqJsonCache Map // JSON Body 缓存
|
||||
reqMu sync.Once // 确保 JSON 只解析一次
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *Context) Mtd(router [3]string) Map {
|
||||
this.Application.Router[router[0]][router[1]][router[2]](this)
|
||||
d := this.RespData
|
||||
this.RespData = nil
|
||||
// Mtd 唯一标志
|
||||
func (that *Context) Mtd(router [3]string) Map {
|
||||
that.Application.Router[router[0]][router[1]][router[2]](that)
|
||||
d := that.RespData
|
||||
that.RespData = nil
|
||||
return d
|
||||
}
|
||||
|
||||
//打印
|
||||
func (this *Context) Display(statu int, data interface{}) {
|
||||
// 打印
|
||||
func (that *Context) Display(statu int, data interface{}) {
|
||||
|
||||
resp := Map{"statu": statu}
|
||||
resp := Map{"status": statu}
|
||||
if statu != 0 {
|
||||
temp := Map{}
|
||||
|
||||
tpe := this.Config.GetMap("error").GetString(ObjToStr(statu))
|
||||
tpe := that.Config.GetMap("error").GetString(ObjToStr(statu))
|
||||
if tpe == "" {
|
||||
this.SetError(errors.New("找不到对应的错误码"))
|
||||
//logFmt(errors.New("找不到对应的错误码"), 2, LOG_WARN)
|
||||
}
|
||||
|
||||
temp["type"] = tpe
|
||||
temp["msg"] = data
|
||||
resp["result"] = temp
|
||||
//兼容android等需要json转对象的服务
|
||||
resp["error"] = temp
|
||||
} else {
|
||||
resp["result"] = data
|
||||
}
|
||||
|
||||
this.RespData = resp
|
||||
that.RespData = resp
|
||||
|
||||
//this.Data=d;
|
||||
//that.Data=d;
|
||||
}
|
||||
|
||||
func (this *Context) View() {
|
||||
if this.RespData == nil {
|
||||
func (that *Context) View() {
|
||||
if that.RespFunc != nil {
|
||||
that.RespFunc()
|
||||
}
|
||||
if that.RespData == nil {
|
||||
return
|
||||
}
|
||||
d, err := json.Marshal(this.RespData)
|
||||
//创建日志
|
||||
if that.Log != nil {
|
||||
that.Log["time"] = time.Now().Format("2006-01-02 15:04")
|
||||
if that.Session("admin_id").Data != nil {
|
||||
that.Log["admin_id"] = that.Session("admin_id").ToCeilInt()
|
||||
}
|
||||
if that.Session("user_id").Data != nil {
|
||||
that.Log["user_id"] = that.Session("user_id").ToCeilInt()
|
||||
}
|
||||
//负载均衡优化
|
||||
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)
|
||||
}
|
||||
|
||||
// ==================== 请求参数获取方法 ====================
|
||||
|
||||
// ReqParam 获取 URL 查询参数,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqParam("id").ToInt()
|
||||
func (that *Context) ReqParam(key string) *Obj {
|
||||
v := that.Req.URL.Query().Get(key)
|
||||
if v == "" {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
|
||||
// ReqForm 获取表单参数,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqForm("name").ToStr()
|
||||
func (that *Context) ReqForm(key string) *Obj {
|
||||
v := that.Req.FormValue(key)
|
||||
if v == "" {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
|
||||
// ReqJson 获取 JSON Body 中的字段,返回 *Obj 支持链式调用
|
||||
// 用法: that.ReqJson("data").ToMap()
|
||||
func (that *Context) ReqJson(key string) *Obj {
|
||||
that.parseJsonBody()
|
||||
if that.reqJsonCache == nil {
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
return &Obj{Data: that.reqJsonCache.Get(key)}
|
||||
}
|
||||
|
||||
// parseJsonBody 解析 JSON Body(只解析一次,并发安全)
|
||||
func (that *Context) parseJsonBody() {
|
||||
that.reqMu.Do(func() {
|
||||
if that.Req.Body == nil {
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(that.Req.Body)
|
||||
if err != nil || len(body) == 0 {
|
||||
return
|
||||
}
|
||||
// 恢复 Body 以便后续代码可以再次读取
|
||||
that.Req.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
that.reqJsonCache = ObjToMap(string(body))
|
||||
})
|
||||
}
|
||||
|
||||
// ReqData 统一获取请求参数,优先级: JSON > Form > URL
|
||||
// 用法: that.ReqData("id").ToInt()
|
||||
func (that *Context) ReqData(key string) *Obj {
|
||||
// 1. 优先从 JSON Body 获取
|
||||
that.parseJsonBody()
|
||||
if that.reqJsonCache != nil {
|
||||
if v := that.reqJsonCache.Get(key); v != nil {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
}
|
||||
// 2. 其次从 Form 获取
|
||||
if v := that.Req.FormValue(key); v != "" {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
// 3. 最后从 URL 参数获取
|
||||
if v := that.Req.URL.Query().Get(key); v != "" {
|
||||
return &Obj{Data: v}
|
||||
}
|
||||
return &Obj{Data: nil}
|
||||
}
|
||||
|
||||
// ReqFile 获取单个上传文件
|
||||
// 用法: file, header, err := that.ReqFile("avatar")
|
||||
func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error) {
|
||||
return that.Req.FormFile(name)
|
||||
}
|
||||
|
||||
// ReqFiles 获取多个同名上传文件(批量上传场景)
|
||||
// 用法: files, err := that.ReqFiles("images")
|
||||
func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error) {
|
||||
if that.Req.MultipartForm == nil {
|
||||
if err := that.Req.ParseMultipartForm(32 << 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if that.Req.MultipartForm == nil || that.Req.MultipartForm.File == nil {
|
||||
return nil, http.ErrMissingFile
|
||||
}
|
||||
files, ok := that.Req.MultipartForm.File[name]
|
||||
if !ok {
|
||||
return nil, http.ErrMissingFile
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import "time"
|
||||
|
||||
type contextBase struct {
|
||||
Error
|
||||
tag int64
|
||||
}
|
||||
|
||||
//唯一标志
|
||||
func (this *contextBase) GetTag() int64 {
|
||||
|
||||
if this.tag == int64(0) {
|
||||
this.tag = time.Now().UnixNano()
|
||||
}
|
||||
return this.tag
|
||||
}
|
||||
@@ -1,935 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
contextBase
|
||||
CacheIns
|
||||
Type string
|
||||
DBCached bool
|
||||
LastQuery string
|
||||
LastData []interface{}
|
||||
ConnectFunc func(err ...*Error) *sql.DB
|
||||
LastErr Error
|
||||
limit Slice
|
||||
Tx *sql.Tx //事务对象
|
||||
|
||||
}
|
||||
|
||||
//设置数据库配置连接
|
||||
func (this *HoTimeDB) SetConnect(connect func(err ...*Error) *sql.DB, err ...*Error) {
|
||||
this.ConnectFunc = connect
|
||||
this.InitDb()
|
||||
}
|
||||
|
||||
//事务,如果action返回true则执行成功;false则回滚
|
||||
func (this *HoTimeDB) Action(action func(db HoTimeDB) bool) bool {
|
||||
db := HoTimeDB{DB: this.DB, CacheIns: this.CacheIns, DBCached: this.DBCached}
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
this.LastErr.SetError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
db.Tx = tx
|
||||
|
||||
result := action(db)
|
||||
|
||||
if !result {
|
||||
db.Tx.Rollback()
|
||||
return result
|
||||
}
|
||||
db.Tx.Commit()
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) InitDb(err ...*Error) Error {
|
||||
if len(err) != 0 {
|
||||
this.LastErr = *(err[0])
|
||||
}
|
||||
this.DB = this.ConnectFunc(&this.LastErr)
|
||||
if this.DB == nil {
|
||||
|
||||
return this.LastErr
|
||||
}
|
||||
e := this.DB.Ping()
|
||||
|
||||
this.LastErr.SetError(e)
|
||||
|
||||
return this.LastErr
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
|
||||
page = (page - 1) * pageRow
|
||||
if page < 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
this.limit = Slice{page, pageRow}
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
|
||||
|
||||
if len(qu) == 1 {
|
||||
qu = append(qu, Map{"LIMIT": this.limit})
|
||||
}
|
||||
if len(qu) == 2 {
|
||||
temp := qu[1].(Map)
|
||||
temp["LIMIT"] = this.limit
|
||||
qu[1] = temp
|
||||
}
|
||||
if len(qu) == 3 {
|
||||
temp := qu[2].(Map)
|
||||
temp["LIMIT"] = this.limit
|
||||
qu[2] = temp
|
||||
}
|
||||
//fmt.Println(qu)
|
||||
data := this.Select(table, qu...)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
//数据库数据解析
|
||||
func (this *HoTimeDB) Row(resl *sql.Rows) []Map {
|
||||
dest := make([]Map, 0)
|
||||
strs, _ := resl.Columns()
|
||||
|
||||
for i := 0; resl.Next(); i++ {
|
||||
lis := make(Map, 0)
|
||||
a := make([]interface{}, len(strs))
|
||||
|
||||
b := make([]interface{}, len(a))
|
||||
for j := 0; j < len(a); j++ {
|
||||
b[j] = &a[j]
|
||||
}
|
||||
resl.Scan(b...)
|
||||
for j := 0; j < len(a); j++ {
|
||||
if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" {
|
||||
lis[strs[j]] = string(a[j].([]byte))
|
||||
} else {
|
||||
lis[strs[j]] = a[j]
|
||||
}
|
||||
|
||||
}
|
||||
//防止int被误读为float64
|
||||
jlis, e := json.Marshal(lis)
|
||||
if e != nil {
|
||||
this.LastErr.SetError(e)
|
||||
} else {
|
||||
lis.JsonToMap(string(jlis), &this.LastErr)
|
||||
}
|
||||
|
||||
dest = append(dest, lis)
|
||||
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
//
|
||||
////code=0,1,2 0 backup all,1 backup data,2 backup ddl
|
||||
//func (this *HoTimeDB) Backup(path string, code int) {
|
||||
// var cmd *exec.Cmd
|
||||
// switch code {
|
||||
// case 0:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
|
||||
// case 1:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]), ObjToStr(Config["dbName"]))
|
||||
// case 2:cmd= exec.Command("mysqldump","--no-data","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"]))
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// stdout, err := cmd.StdoutPipe()
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
//
|
||||
// if err := cmd.Start(); err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
//
|
||||
// bytes, err := ioutil.ReadAll(stdout)
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// }
|
||||
// err = ioutil.WriteFile(path, bytes, 0644)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// return ;
|
||||
// //
|
||||
// //db := ``
|
||||
// //fmt.Println(db)
|
||||
// //
|
||||
// //tables := this.Query("show tables")
|
||||
// //lth := len(tables)
|
||||
// //if lth == 0 {
|
||||
// // return
|
||||
// //}
|
||||
// //for k, _ := range tables[0] {
|
||||
// // db = Substr(k, 10, len(k))
|
||||
// //}
|
||||
// //
|
||||
// //fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
// //fd.Write([]byte("/*datetime " + time.Now().Format("2006-01-02 15:04:05") + " */ \r\n"))
|
||||
// //fd.Close()
|
||||
// //
|
||||
// //for i := 0; i < lth; i++ {
|
||||
// // tt := tables[i]["Tables_in_"+db].(string)
|
||||
// // this.backupSave(path, tt, code)
|
||||
// // debug.FreeOSMemory()
|
||||
// //}
|
||||
//
|
||||
//}
|
||||
|
||||
func (this *HoTimeDB) backupSave(path string, tt string, code int) {
|
||||
fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||
defer fd.Close()
|
||||
|
||||
str := "\r\n"
|
||||
if code == 0 || code == 2 {
|
||||
str += this.backupDdl(tt)
|
||||
}
|
||||
|
||||
if code == 0 || code == 1 {
|
||||
str += "insert into `" + tt + "`\r\n\r\n("
|
||||
str += this.backupCol(tt)
|
||||
}
|
||||
|
||||
fd.Write([]byte(str))
|
||||
}
|
||||
func (this *HoTimeDB) backupDdl(tt string) string {
|
||||
|
||||
data := this.Query("show create table " + tt)
|
||||
if len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) backupCol(tt string) string {
|
||||
str := ""
|
||||
data := this.Select(tt, "*")
|
||||
|
||||
lthData := len(data)
|
||||
|
||||
if lthData == 0 {
|
||||
return str
|
||||
}
|
||||
lthCol := len(data[0])
|
||||
col := make([]string, lthCol)
|
||||
tempLthData := 0
|
||||
for k, _ := range data[0] {
|
||||
|
||||
if tempLthData == lthCol-1 {
|
||||
str += "`" + k + "`)"
|
||||
} else {
|
||||
str += "`" + k + "`,"
|
||||
}
|
||||
col[tempLthData] = k
|
||||
tempLthData++
|
||||
}
|
||||
|
||||
str += " values"
|
||||
|
||||
for j := 0; j < lthData; j++ {
|
||||
|
||||
for m := 0; m < lthCol; m++ {
|
||||
|
||||
if m == 0 {
|
||||
str += "("
|
||||
}
|
||||
|
||||
v := "NULL"
|
||||
if data[j][col[m]] != nil {
|
||||
v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'"
|
||||
}
|
||||
|
||||
if m == lthCol-1 {
|
||||
str += v + ")"
|
||||
|
||||
} else {
|
||||
str += v + ","
|
||||
}
|
||||
}
|
||||
if j == lthData-1 {
|
||||
str += ";\r\n\r\n"
|
||||
} else {
|
||||
str += ",\r\n\r\n"
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) md5(query string, args ...interface{}) string {
|
||||
strByte, _ := json.Marshal(args)
|
||||
str := Md5(query + ":" + string(strByte))
|
||||
return str
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Query(query string, args ...interface{}) []Map {
|
||||
|
||||
//fmt.Println(query)
|
||||
var err error
|
||||
var resl *sql.Rows
|
||||
|
||||
this.LastQuery = query
|
||||
this.LastData = args
|
||||
|
||||
if this.DB == nil {
|
||||
err = errors.New("没有初始化数据库")
|
||||
this.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if this.Tx != nil {
|
||||
resl, err = this.Tx.Query(query, args...)
|
||||
} else {
|
||||
resl, err = this.DB.Query(query, args...)
|
||||
}
|
||||
|
||||
this.LastErr.SetError(err)
|
||||
if err != nil {
|
||||
if err = this.DB.Ping(); err != nil {
|
||||
this.LastErr.SetError(err)
|
||||
this.InitDb()
|
||||
if this.LastErr.GetError() != nil {
|
||||
return nil
|
||||
}
|
||||
return this.Query(query, args...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.Row(resl)
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, Error) {
|
||||
|
||||
this.LastQuery = query
|
||||
this.LastData = args
|
||||
var e error
|
||||
var resl sql.Result
|
||||
|
||||
if this.DB == nil {
|
||||
err := errors.New("没有初始化数据库")
|
||||
this.LastErr.SetError(err)
|
||||
return nil, this.LastErr
|
||||
}
|
||||
|
||||
if this.Tx != nil {
|
||||
resl, e = this.Tx.Exec(query, args...)
|
||||
} else {
|
||||
resl, e = this.DB.Exec(query, args...)
|
||||
}
|
||||
|
||||
this.LastErr.SetError(e)
|
||||
|
||||
//判断是否连接断开了
|
||||
if e != nil {
|
||||
|
||||
if e = this.DB.Ping(); e != nil {
|
||||
this.LastErr.SetError(e)
|
||||
this.InitDb()
|
||||
if this.LastErr.GetError() != nil {
|
||||
return resl, this.LastErr
|
||||
}
|
||||
return this.Exec(query, args...)
|
||||
}
|
||||
}
|
||||
|
||||
return resl, this.LastErr
|
||||
}
|
||||
|
||||
//func (this *HoTimeDB)copy(data []Map)[]Map{
|
||||
// if data==nil{
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// lth:=len(data)
|
||||
//
|
||||
// res:=make([]Map,lth)
|
||||
//
|
||||
//
|
||||
// for i:=0;i<lth;i++{
|
||||
//
|
||||
// res[i]=DeepCopy(data[i]).(Map)
|
||||
// }
|
||||
//
|
||||
// return res
|
||||
//
|
||||
//}
|
||||
func (this *HoTimeDB) Select(table string, qu ...interface{}) []Map {
|
||||
|
||||
query := "SELECT"
|
||||
where := Map{}
|
||||
qs := make([]interface{}, 0)
|
||||
intQs, intWhere := 0, 1
|
||||
join := false
|
||||
if len(qu) == 3 {
|
||||
intQs = 1
|
||||
intWhere = 2
|
||||
join = true
|
||||
|
||||
}
|
||||
|
||||
if len(qu) > 0 {
|
||||
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
|
||||
query += " " + qu[intQs].(string)
|
||||
} else {
|
||||
for i := 0; i < len(qu[intQs].(Slice)); i++ {
|
||||
k := qu[intQs].(Slice)[i].(string)
|
||||
if strings.Contains(k, " AS ") {
|
||||
|
||||
query += " " + k + " "
|
||||
|
||||
} else {
|
||||
query += " `" + k + "` "
|
||||
}
|
||||
|
||||
if i+1 != len(qu[intQs].(Slice)) {
|
||||
query = query + ", "
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
query += " *"
|
||||
}
|
||||
|
||||
query += " FROM " + table
|
||||
|
||||
if join {
|
||||
for k, v := range qu[0].(Map) {
|
||||
switch Substr(k, 0, 3) {
|
||||
case "[>]":
|
||||
query += " LEFT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
|
||||
case "[<]":
|
||||
query += " RIGHT JOIN " + Substr(k, 3, len(k)-3) + " ON " + v.(string)
|
||||
}
|
||||
switch Substr(k, 0, 4) {
|
||||
case "[<>]":
|
||||
query += " FULL JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
|
||||
case "[><]":
|
||||
query += " INNER JOIN " + Substr(k, 4, len(k)-4) + " ON " + v.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(qu) > 1 {
|
||||
where = qu[intWhere].(Map)
|
||||
}
|
||||
|
||||
temp, resWhere := this.where(where)
|
||||
|
||||
query += temp
|
||||
qs = append(qs, resWhere...)
|
||||
md5 := this.md5(query, qs...)
|
||||
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
//如果缓存有则从缓存取
|
||||
cacheData := this.Cache(table + ":" + md5)
|
||||
|
||||
if cacheData.Data != nil {
|
||||
return cacheData.ToMapArray()
|
||||
}
|
||||
}
|
||||
|
||||
//无缓存则数据库取
|
||||
res := this.Query(query, qs...)
|
||||
|
||||
if res == nil {
|
||||
res = []Map{}
|
||||
}
|
||||
|
||||
//缓存
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
|
||||
this.Cache(table+":"+md5, res)
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Get(table string, qu ...interface{}) Map {
|
||||
//fmt.Println(qu)
|
||||
if len(qu) == 1 {
|
||||
qu = append(qu, Map{"LIMIT": 1})
|
||||
}
|
||||
if len(qu) == 2 {
|
||||
temp := qu[1].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[1] = temp
|
||||
}
|
||||
if len(qu) == 3 {
|
||||
temp := qu[2].(Map)
|
||||
temp["LIMIT"] = 1
|
||||
qu[2] = temp
|
||||
}
|
||||
//fmt.Println(qu)
|
||||
data := this.Select(table, qu...)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[0]
|
||||
}
|
||||
|
||||
/**
|
||||
** 计数
|
||||
*/
|
||||
func (this *HoTimeDB) Count(table string, qu ...interface{}) int {
|
||||
req := []interface{}{}
|
||||
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "COUNT(*)")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
//req=append(req,qu...)
|
||||
data := this.Select(table, req...)
|
||||
//fmt.Println(data)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
//res,_:=StrToInt(data[0]["COUNT(*)"].(string))
|
||||
res := ObjToStr(data[0]["COUNT(*)"])
|
||||
count, _ := StrToInt(res)
|
||||
return count
|
||||
|
||||
}
|
||||
|
||||
var condition = []string{"AND", "OR"}
|
||||
var vcond = []string{"GROUP", "ORDER", "LIMIT"}
|
||||
|
||||
//where语句解析
|
||||
func (this *HoTimeDB) where(data Map) (string, []interface{}) {
|
||||
|
||||
where := ""
|
||||
|
||||
res := make([]interface{}, 0)
|
||||
//AND OR判断
|
||||
for k, v := range data {
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
|
||||
if condition[i] == k {
|
||||
|
||||
tw, ts := this.cond(k, v.(Map))
|
||||
|
||||
where += tw
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
y := 0
|
||||
for j := 0; j < len(vcond); j++ {
|
||||
|
||||
if vcond[j] == k {
|
||||
break
|
||||
}
|
||||
y++
|
||||
}
|
||||
if x == len(condition) && y == len(vcond) {
|
||||
tv, vv := this.varCond(k, v)
|
||||
|
||||
where += tv
|
||||
res = append(res, vv...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(where) != 0 {
|
||||
where = " WHERE " + where
|
||||
}
|
||||
|
||||
//特殊字符
|
||||
for j := 0; j < len(vcond); j++ {
|
||||
for k, v := range data {
|
||||
if vcond[j] == k {
|
||||
if k == "ORDER" {
|
||||
where += " " + k + " BY "
|
||||
//fmt.Println(reflect.ValueOf(v).Type())
|
||||
|
||||
//break
|
||||
} else if k == "GROUP" {
|
||||
|
||||
where += " " + k + " BY "
|
||||
} else {
|
||||
|
||||
where += " " + k
|
||||
}
|
||||
|
||||
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
where += " " + ObjToStr(v.(Slice)[i])
|
||||
|
||||
if len(v.(Slice)) != i+1 {
|
||||
where += ","
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
where += " " + ObjToStr(v)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
length := len(k)
|
||||
if length > 4 {
|
||||
def := false
|
||||
|
||||
switch Substr(k, length-3, 3) {
|
||||
case "[>]":
|
||||
k = strings.Replace(k, "[>]", "", -1)
|
||||
where += "`" + k + "`>? "
|
||||
res = append(res, v)
|
||||
case "[<]":
|
||||
k = strings.Replace(k, "[<]", "", -1)
|
||||
where += "`" + k + "`<? "
|
||||
res = append(res, v)
|
||||
case "[!]":
|
||||
k = strings.Replace(k, "[!]", "", -1)
|
||||
where, res = this.notIn(k, v, where, res)
|
||||
case "[#]":
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + k + "=" + ObjToStr(v)
|
||||
case "[#!]":
|
||||
k = strings.Replace(k, "[#!]", "", -1)
|
||||
where += " " + k + "!=" + ObjToStr(v)
|
||||
case "[!#]":
|
||||
k = strings.Replace(k, "[!#]", "", -1)
|
||||
where += " " + k + "!=" + ObjToStr(v)
|
||||
case "[~]":
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = "%" + ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[!~]": //左边任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = "%" + ObjToStr(v)
|
||||
res = append(res, v)
|
||||
case "[~!]": //右边任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
v = ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[~~]": //手动任意
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
where += "`" + k + "` LIKE ? "
|
||||
//v = ObjToStr(v)
|
||||
res = append(res, v)
|
||||
default:
|
||||
def = true
|
||||
|
||||
}
|
||||
if def {
|
||||
switch Substr(k, length-4, 4) {
|
||||
case "[>=]":
|
||||
k = strings.Replace(k, "[>=]", "", -1)
|
||||
where += "`" + k + "`>=? "
|
||||
res = append(res, v)
|
||||
case "[<=]":
|
||||
k = strings.Replace(k, "[<=]", "", -1)
|
||||
where += "`" + k + "`<=? "
|
||||
res = append(res, v)
|
||||
case "[><]":
|
||||
k = strings.Replace(k, "[><]", "", -1)
|
||||
where += "`" + k + "` NOT BETWEEN ? AND ? "
|
||||
res = append(res, v.(Slice)[0])
|
||||
res = append(res, v.(Slice)[1])
|
||||
case "[<>]":
|
||||
k = strings.Replace(k, "[<>]", "", -1)
|
||||
where += "`" + k + "` BETWEEN ? AND ? "
|
||||
res = append(res, v.(Slice)[0])
|
||||
res = append(res, v.(Slice)[1])
|
||||
default:
|
||||
if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
if len(v.(Slice)) == 0 {
|
||||
where += ") "
|
||||
} else {
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
where += "`" + k + "`=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else if k == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else {
|
||||
//fmt.Println(reflect.ValueOf(v).Type().String())
|
||||
if v == nil {
|
||||
where += "`" + k + "` IS NULL"
|
||||
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
|
||||
//fmt.Println(v)
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
|
||||
where += "`" + k + "` IN ("
|
||||
res = append(res, (v.([]interface{}))...)
|
||||
for i := 0; i < len(v.([]interface{})); i++ {
|
||||
if i+1 != len(v.([]interface{})) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else {
|
||||
|
||||
where += "`" + k + "`=? "
|
||||
res = append(res, v)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// this.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1})
|
||||
func (this *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
//where:=""
|
||||
//fmt.Println(reflect.ValueOf(v).Type().String())
|
||||
if v == nil {
|
||||
|
||||
where += "`" + k + "` IS NOT NULL "
|
||||
|
||||
} else if reflect.ValueOf(v).Type().String() == "hotime.Slice" {
|
||||
where += "`" + k + "` NOT IN ("
|
||||
res = append(res, (v.(Slice))...)
|
||||
for i := 0; i < len(v.(Slice)); i++ {
|
||||
if i+1 != len(v.(Slice)) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else if reflect.ValueOf(v).Type().String() == "[]interface {}" {
|
||||
where += "`" + k + "` NOT IN ("
|
||||
res = append(res, (v.([]interface{}))...)
|
||||
for i := 0; i < len(v.([]interface{})); i++ {
|
||||
if i+1 != len(v.([]interface{})) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
//res=append(res,(v.(Slice))[i])
|
||||
}
|
||||
} else {
|
||||
|
||||
where += "`" + k + "` !=? "
|
||||
res = append(res, v)
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
|
||||
where := " "
|
||||
res := make([]interface{}, 0)
|
||||
lens := len(data)
|
||||
//fmt.Println(lens)
|
||||
for k, v := range data {
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
|
||||
if condition[i] == k {
|
||||
|
||||
tw, ts := this.cond(k, v.(Map))
|
||||
if lens--; lens <= 0 {
|
||||
//fmt.Println(lens)
|
||||
where += "(" + tw + ") "
|
||||
} else {
|
||||
where += "(" + tw + ") " + tag + " "
|
||||
}
|
||||
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
if x == len(condition) {
|
||||
|
||||
tv, vv := this.varCond(k, v)
|
||||
|
||||
res = append(res, vv...)
|
||||
if lens--; lens <= 0 {
|
||||
where += tv + ""
|
||||
} else {
|
||||
where += tv + " " + tag + " "
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
//更新数据
|
||||
func (this *HoTimeDB) Update(table string, data Map, where Map) int64 {
|
||||
|
||||
query := "UPDATE " + table + " SET "
|
||||
//UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson'
|
||||
qs := make([]interface{}, 0)
|
||||
tp := len(data)
|
||||
|
||||
for k, v := range data {
|
||||
vstr := "?"
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
|
||||
vstr = ObjToStr(v)
|
||||
} else {
|
||||
qs = append(qs, v)
|
||||
}
|
||||
query += "`" + k + "`=" + vstr + ""
|
||||
if tp--; tp != 0 {
|
||||
query += ", "
|
||||
}
|
||||
}
|
||||
|
||||
temp, resWhere := this.where(where)
|
||||
//fmt.Println(resWhere)
|
||||
|
||||
query += temp
|
||||
qs = append(qs, resWhere...)
|
||||
|
||||
res, err := this.Exec(query, qs...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
//如果更新成功,则删除缓存
|
||||
if rows != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
func (this *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
|
||||
|
||||
query := "DELETE FROM " + table + " "
|
||||
|
||||
temp, resWhere := this.where(data)
|
||||
query += temp
|
||||
|
||||
res, err := this.Exec(query, resWhere...)
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
//如果删除成功,删除对应缓存
|
||||
if rows != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
//return 0
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
//插入新数据
|
||||
func (this *HoTimeDB) Insert(table string, data map[string]interface{}) int64 {
|
||||
|
||||
values := make([]interface{}, 0)
|
||||
queryString := " ("
|
||||
valueString := " ("
|
||||
|
||||
lens := len(data)
|
||||
tempLen := 0
|
||||
for k, v := range data {
|
||||
tempLen++
|
||||
values = append(values, v)
|
||||
if tempLen < lens {
|
||||
queryString += "`" + k + "`,"
|
||||
valueString += "?,"
|
||||
} else {
|
||||
queryString += "`" + k + "`) "
|
||||
valueString += "?);"
|
||||
}
|
||||
|
||||
}
|
||||
query := "INSERT INTO " + table + queryString + "VALUES" + valueString
|
||||
|
||||
res, err := this.Exec(query, values...)
|
||||
|
||||
id := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
id, this.LastErr.err = res.LastInsertId()
|
||||
|
||||
}
|
||||
|
||||
//如果插入成功,删除缓存
|
||||
if id != 0 {
|
||||
if this.DBCached && this.CacheIns != nil {
|
||||
this.Cache(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Println(id)
|
||||
return id
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
# HoTimeDB API 快速参考
|
||||
|
||||
## 条件查询语法规则
|
||||
|
||||
**新版本改进:**
|
||||
- 多条件自动用 AND 连接,无需手动包装
|
||||
- 关键字支持大小写(如 `LIMIT` 和 `limit` 都有效)
|
||||
- 新增 `HAVING` 和独立 `OFFSET` 支持
|
||||
|
||||
```go
|
||||
// ✅ 推荐:简化语法(多条件自动 AND)
|
||||
Map{"status": 1, "age[>]": 18}
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
|
||||
// ✅ 仍然支持:显式 AND 包装(向后兼容)
|
||||
Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
}
|
||||
|
||||
// ✅ 混合条件和特殊关键字
|
||||
Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"ORDER": "id DESC", // 或 "order": "id DESC"
|
||||
"LIMIT": 10, // 或 "limit": 10
|
||||
}
|
||||
```
|
||||
|
||||
## 基本方法
|
||||
|
||||
### 数据库连接
|
||||
```go
|
||||
database.SetConnect(func() (master, slave *sql.DB) { ... })
|
||||
database.InitDb()
|
||||
```
|
||||
|
||||
### 链式查询构建器
|
||||
```go
|
||||
// 创建查询构建器
|
||||
builder := database.Table("tablename")
|
||||
|
||||
// 设置条件
|
||||
builder.Where(key, value)
|
||||
builder.And(key, value) 或 builder.And(map)
|
||||
builder.Or(key, value) 或 builder.Or(map)
|
||||
|
||||
// JOIN操作
|
||||
builder.LeftJoin(table, condition)
|
||||
builder.RightJoin(table, condition)
|
||||
builder.InnerJoin(table, condition)
|
||||
builder.FullJoin(table, condition)
|
||||
builder.Join(map) // 通用JOIN
|
||||
|
||||
// 排序和分组
|
||||
builder.Order(fields...)
|
||||
builder.Group(fields...)
|
||||
builder.Limit(args...)
|
||||
builder.Having(map) // 新增
|
||||
|
||||
// 分页
|
||||
builder.Page(page, pageSize)
|
||||
builder.Offset(offset) // 新增
|
||||
|
||||
// 执行查询
|
||||
builder.Select(fields...) // 返回 []Map
|
||||
builder.Get(fields...) // 返回 Map
|
||||
builder.Count() // 返回 int
|
||||
builder.Update(data) // 返回 int64
|
||||
builder.Delete() // 返回 int64
|
||||
```
|
||||
|
||||
## CRUD 操作
|
||||
|
||||
### 查询 (Select)
|
||||
```go
|
||||
// 基本查询
|
||||
data := database.Select("table")
|
||||
data := database.Select("table", "field1,field2")
|
||||
data := database.Select("table", []string{"field1", "field2"})
|
||||
data := database.Select("table", "*", whereMap)
|
||||
|
||||
// 带JOIN查询
|
||||
data := database.Select("table", joinSlice, "fields", whereMap)
|
||||
```
|
||||
|
||||
### 获取单条 (Get)
|
||||
```go
|
||||
// 自动添加 LIMIT 1
|
||||
row := database.Get("table", "fields", whereMap)
|
||||
```
|
||||
|
||||
### 插入 (Insert)
|
||||
```go
|
||||
id := database.Insert("table", dataMap)
|
||||
// 返回新插入记录的ID
|
||||
```
|
||||
|
||||
### 批量插入 (BatchInsert) - 新增
|
||||
```go
|
||||
// 使用 []Map 格式,更直观简洁
|
||||
affected := database.BatchInsert("table", []Map{
|
||||
{"col1": "val1", "col2": "val2", "col3": "val3"},
|
||||
{"col1": "val4", "col2": "val5", "col3": "val6"},
|
||||
})
|
||||
// 返回受影响的行数
|
||||
|
||||
// 支持 [#] 标记直接 SQL
|
||||
affected := database.BatchInsert("log", []Map{
|
||||
{"user_id": 1, "created_time[#]": "NOW()"},
|
||||
{"user_id": 2, "created_time[#]": "NOW()"},
|
||||
})
|
||||
```
|
||||
|
||||
### 更新 (Update)
|
||||
```go
|
||||
affected := database.Update("table", dataMap, whereMap)
|
||||
// 返回受影响的行数
|
||||
```
|
||||
|
||||
### Upsert - 新增
|
||||
```go
|
||||
// 使用 Slice 格式
|
||||
affected := database.Upsert("table",
|
||||
dataMap, // 插入数据
|
||||
Slice{"unique_key"}, // 唯一键
|
||||
Slice{"col1", "col2"}, // 冲突时更新的字段
|
||||
)
|
||||
|
||||
// 也支持可变参数
|
||||
affected := database.Upsert("table", dataMap, Slice{"id"}, "col1", "col2")
|
||||
// 返回受影响的行数
|
||||
```
|
||||
|
||||
### 删除 (Delete)
|
||||
```go
|
||||
affected := database.Delete("table", whereMap)
|
||||
// 返回删除的行数
|
||||
```
|
||||
|
||||
## 聚合函数
|
||||
|
||||
### 计数
|
||||
```go
|
||||
count := database.Count("table")
|
||||
count := database.Count("table", whereMap)
|
||||
count := database.Count("table", joinSlice, whereMap)
|
||||
```
|
||||
|
||||
### 求和
|
||||
```go
|
||||
sum := database.Sum("table", "column")
|
||||
sum := database.Sum("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 平均值 - 新增
|
||||
```go
|
||||
avg := database.Avg("table", "column")
|
||||
avg := database.Avg("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 最大值 - 新增
|
||||
```go
|
||||
max := database.Max("table", "column")
|
||||
max := database.Max("table", "column", whereMap)
|
||||
```
|
||||
|
||||
### 最小值 - 新增
|
||||
```go
|
||||
min := database.Min("table", "column")
|
||||
min := database.Min("table", "column", whereMap)
|
||||
```
|
||||
|
||||
## 分页查询
|
||||
```go
|
||||
// 设置分页
|
||||
database.Page(page, pageSize)
|
||||
|
||||
// 分页查询
|
||||
data := database.Page(page, pageSize).PageSelect("table", "fields", whereMap)
|
||||
```
|
||||
|
||||
## 条件语法参考
|
||||
|
||||
### 比较操作符
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": value` | `field = ?` | 等于 |
|
||||
| `"field[!]": value` | `field != ?` | 不等于 |
|
||||
| `"field[>]": value` | `field > ?` | 大于 |
|
||||
| `"field[>=]": value` | `field >= ?` | 大于等于 |
|
||||
| `"field[<]": value` | `field < ?` | 小于 |
|
||||
| `"field[<=]": value` | `field <= ?` | 小于等于 |
|
||||
|
||||
### 模糊查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[~]": "keyword"` | `field LIKE '%keyword%'` | 包含 |
|
||||
| `"field[~!]": "keyword"` | `field LIKE 'keyword%'` | 以...开头 |
|
||||
| `"field[!~]": "keyword"` | `field LIKE '%keyword'` | 以...结尾 |
|
||||
| `"field[~~]": "%keyword%"` | `field LIKE '%keyword%'` | 手动LIKE |
|
||||
|
||||
### 范围查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[<>]": [min, max]` | `field BETWEEN ? AND ?` | 区间内 |
|
||||
| `"field[><]": [min, max]` | `field NOT BETWEEN ? AND ?` | 区间外 |
|
||||
|
||||
### 集合查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": [v1, v2, v3]` | `field IN (?, ?, ?)` | 在集合中 |
|
||||
| `"field[!]": [v1, v2, v3]` | `field NOT IN (?, ?, ?)` | 不在集合中 |
|
||||
|
||||
### NULL查询
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": nil` | `field IS NULL` | 为空 |
|
||||
| `"field[!]": nil` | `field IS NOT NULL` | 不为空 |
|
||||
|
||||
### 直接SQL
|
||||
| 写法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field[#]": "NOW()"` | `field = NOW()` | 直接SQL函数 |
|
||||
| `"[##]": "a > b"` | `a > b` | 直接SQL片段 |
|
||||
| `"field[#!]": "1"` | `field != 1` | 不等于(不参数化) |
|
||||
|
||||
## 逻辑连接符
|
||||
|
||||
### AND 条件
|
||||
```go
|
||||
// 简化语法(推荐)
|
||||
whereMap := Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
}
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
|
||||
// 显式 AND(向后兼容)
|
||||
whereMap := Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### OR 条件
|
||||
```go
|
||||
whereMap := Map{
|
||||
"OR": Map{
|
||||
"status": 1,
|
||||
"type": 2,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 嵌套条件
|
||||
```go
|
||||
whereMap := Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"OR": Map{
|
||||
"age[<]": 30,
|
||||
"level[>]": 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## JOIN 语法
|
||||
|
||||
### 传统语法
|
||||
```go
|
||||
joinSlice := Slice{
|
||||
Map{"[>]profile": "user.id = profile.user_id"}, // LEFT JOIN
|
||||
Map{"[<]department": "user.dept_id = department.id"}, // RIGHT JOIN
|
||||
Map{"[><]role": "user.role_id = role.id"}, // INNER JOIN
|
||||
Map{"[<>]group": "user.group_id = group.id"}, // FULL JOIN
|
||||
}
|
||||
```
|
||||
|
||||
### 链式语法
|
||||
```go
|
||||
builder.LeftJoin("profile", "user.id = profile.user_id")
|
||||
builder.RightJoin("department", "user.dept_id = department.id")
|
||||
builder.InnerJoin("role", "user.role_id = role.id")
|
||||
builder.FullJoin("group", "user.group_id = group.id")
|
||||
```
|
||||
|
||||
## 特殊字段语法
|
||||
|
||||
### ORDER BY
|
||||
```go
|
||||
Map{
|
||||
"ORDER": []string{"created_time DESC", "id ASC"},
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"order": "created_time DESC", // 支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### GROUP BY
|
||||
```go
|
||||
Map{
|
||||
"GROUP": []string{"department", "level"},
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"group": "department", // 支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### HAVING - 新增
|
||||
```go
|
||||
Map{
|
||||
"GROUP": "dept_id",
|
||||
"HAVING": Map{
|
||||
"COUNT(*).[>]": 5,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### LIMIT
|
||||
```go
|
||||
Map{
|
||||
"LIMIT": []int{10, 20}, // offset 10, limit 20
|
||||
}
|
||||
// 或
|
||||
Map{
|
||||
"limit": 20, // limit 20,支持小写
|
||||
}
|
||||
```
|
||||
|
||||
### OFFSET - 新增
|
||||
```go
|
||||
Map{
|
||||
"LIMIT": 10,
|
||||
"OFFSET": 20, // 独立的 OFFSET
|
||||
}
|
||||
```
|
||||
|
||||
## 事务处理
|
||||
```go
|
||||
success := database.Action(func(tx HoTimeDB) bool {
|
||||
// 在这里执行数据库操作
|
||||
// 返回 true 提交事务
|
||||
// 返回 false 回滚事务
|
||||
|
||||
id := tx.Insert("table", data)
|
||||
if id == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
affected := tx.Update("table2", data2, where2)
|
||||
if affected == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
return true // 提交
|
||||
})
|
||||
```
|
||||
|
||||
## 原生SQL执行
|
||||
|
||||
### 查询
|
||||
```go
|
||||
results := database.Query("SELECT * FROM user WHERE age > ?", 18)
|
||||
```
|
||||
|
||||
### 执行
|
||||
```go
|
||||
result, err := database.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100)
|
||||
affected, _ := result.RowsAffected()
|
||||
```
|
||||
|
||||
## PostgreSQL 支持 - 新增
|
||||
|
||||
```go
|
||||
// 配置 PostgreSQL
|
||||
database := &db.HoTimeDB{
|
||||
Type: "postgres", // 设置类型
|
||||
}
|
||||
|
||||
// 框架自动处理差异:
|
||||
// - 占位符: ? -> $1, $2, $3...
|
||||
// - 引号: `name` -> "name"
|
||||
// - Upsert: ON DUPLICATE KEY -> ON CONFLICT
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
```go
|
||||
// 检查最后的错误
|
||||
if database.LastErr.GetError() != nil {
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
}
|
||||
|
||||
// 查看最后执行的SQL
|
||||
fmt.Println("SQL:", database.LastQuery)
|
||||
fmt.Println("参数:", database.LastData)
|
||||
```
|
||||
|
||||
## 工具方法
|
||||
|
||||
### 数据库信息
|
||||
```go
|
||||
prefix := database.GetPrefix() // 获取表前缀
|
||||
dbType := database.GetType() // 获取数据库类型
|
||||
dialect := database.GetDialect() // 获取方言适配器
|
||||
```
|
||||
|
||||
### 设置模式
|
||||
```go
|
||||
database.Mode = 0 // 生产模式
|
||||
database.Mode = 1 // 测试模式
|
||||
database.Mode = 2 // 开发模式(输出SQL日志)
|
||||
```
|
||||
|
||||
## 常用查询模式
|
||||
|
||||
### 分页列表查询
|
||||
```go
|
||||
// 获取总数
|
||||
total := database.Count("user", Map{"status": 1})
|
||||
|
||||
// 分页数据
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
Order("created_time DESC").
|
||||
Page(page, pageSize).
|
||||
Select("id,name,email,created_time")
|
||||
|
||||
// 计算分页信息
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
```
|
||||
|
||||
### 关联查询
|
||||
```go
|
||||
orders := database.Table("order").
|
||||
LeftJoin("user", "order.user_id = user.id").
|
||||
LeftJoin("product", "order.product_id = product.id").
|
||||
Where("order.status", "paid").
|
||||
Select(`
|
||||
order.*,
|
||||
user.name as user_name,
|
||||
product.title as product_title
|
||||
`)
|
||||
```
|
||||
|
||||
### 统计查询
|
||||
```go
|
||||
stats := database.Select("order",
|
||||
"user_id, COUNT(*) as order_count, SUM(amount) as total_amount",
|
||||
Map{
|
||||
"status": "paid",
|
||||
"created_time[>]": "2023-01-01",
|
||||
"GROUP": "user_id",
|
||||
"ORDER": "total_amount DESC",
|
||||
})
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
```go
|
||||
// 批量插入(使用 []Map 格式)
|
||||
affected := database.BatchInsert("user", []Map{
|
||||
{"name": "用户1", "email": "user1@example.com", "status": 1},
|
||||
{"name": "用户2", "email": "user2@example.com", "status": 1},
|
||||
{"name": "用户3", "email": "user3@example.com", "status": 1},
|
||||
})
|
||||
|
||||
// Upsert(插入或更新,使用 Slice 格式)
|
||||
affected := database.Upsert("user",
|
||||
Map{"id": 1, "name": "新名称", "email": "new@example.com"},
|
||||
Slice{"id"},
|
||||
Slice{"name", "email"},
|
||||
)
|
||||
```
|
||||
|
||||
## 链式调用完整示例
|
||||
|
||||
```go
|
||||
// 复杂查询链式调用
|
||||
result := database.Table("order").
|
||||
LeftJoin("user", "order.user_id = user.id").
|
||||
LeftJoin("product", "order.product_id = product.id").
|
||||
Where("order.status", "paid").
|
||||
And("order.created_time[>]", "2023-01-01").
|
||||
And(Map{
|
||||
"OR": Map{
|
||||
"user.level": "vip",
|
||||
"order.amount[>]": 1000,
|
||||
},
|
||||
}).
|
||||
Group("user.id").
|
||||
Having(Map{"total_amount[>]": 500}).
|
||||
Order("total_amount DESC").
|
||||
Page(1, 20).
|
||||
Select(`
|
||||
user.id,
|
||||
user.name,
|
||||
user.email,
|
||||
COUNT(order.id) as order_count,
|
||||
SUM(order.amount) as total_amount
|
||||
`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*快速参考版本: 2.0*
|
||||
*更新日期: 2026年1月*
|
||||
@@ -0,0 +1,830 @@
|
||||
# HoTimeDB ORM 使用说明书
|
||||
|
||||
## 概述
|
||||
|
||||
HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL等数据库,并集成了缓存、事务、链式查询等功能。
|
||||
|
||||
## 目录
|
||||
|
||||
- [快速开始](#快速开始)
|
||||
- [数据库配置](#数据库配置)
|
||||
- [基本操作](#基本操作)
|
||||
- [查询(Select)](#查询select)
|
||||
- [获取单条记录(Get)](#获取单条记录get)
|
||||
- [插入(Insert)](#插入insert)
|
||||
- [批量插入(BatchInsert)](#批量插入batchinsert)
|
||||
- [更新(Update)](#更新update)
|
||||
- [Upsert操作](#upsert操作)
|
||||
- [删除(Delete)](#删除delete)
|
||||
- [链式查询构建器](#链式查询构建器)
|
||||
- [条件查询语法](#条件查询语法)
|
||||
- [JOIN操作](#join操作)
|
||||
- [分页查询](#分页查询)
|
||||
- [聚合函数](#聚合函数)
|
||||
- [事务处理](#事务处理)
|
||||
- [缓存机制](#缓存机制)
|
||||
- [PostgreSQL支持](#postgresql支持)
|
||||
- [高级特性](#高级特性)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 初始化数据库连接
|
||||
|
||||
```go
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/db"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// 创建连接函数
|
||||
func createConnection() (master, slave *sql.DB) {
|
||||
master, _ = sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
|
||||
// slave是可选的,用于读写分离
|
||||
slave = master // 或者连接到从数据库
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化HoTimeDB
|
||||
database := &db.HoTimeDB{
|
||||
Type: "mysql", // 可选:mysql, sqlite3, postgres
|
||||
}
|
||||
database.SetConnect(createConnection)
|
||||
```
|
||||
|
||||
## 数据库配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
```go
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
ContextBase
|
||||
DBName string
|
||||
*cache.HoTimeCache
|
||||
Log *logrus.Logger
|
||||
Type string // 数据库类型:mysql, sqlite3, postgres
|
||||
Prefix string // 表前缀
|
||||
LastQuery string // 最后执行的SQL
|
||||
LastData []interface{} // 最后的参数
|
||||
ConnectFunc func(err ...*Error) (*sql.DB, *sql.DB)
|
||||
LastErr *Error
|
||||
limit Slice
|
||||
*sql.Tx // 事务对象
|
||||
SlaveDB *sql.DB // 从数据库
|
||||
Mode int // 0生产模式,1测试模式,2开发模式
|
||||
Dialect Dialect // 数据库方言适配器
|
||||
}
|
||||
```
|
||||
|
||||
### 设置表前缀
|
||||
|
||||
```go
|
||||
database.Prefix = "app_"
|
||||
```
|
||||
|
||||
### 设置运行模式
|
||||
|
||||
```go
|
||||
database.Mode = 2 // 开发模式,会输出SQL日志
|
||||
```
|
||||
|
||||
## 基本操作
|
||||
|
||||
### 查询(Select)
|
||||
|
||||
#### 基本查询
|
||||
|
||||
```go
|
||||
// 查询所有字段
|
||||
users := database.Select("user")
|
||||
|
||||
// 查询指定字段
|
||||
users := database.Select("user", "id,name,email")
|
||||
|
||||
// 查询指定字段(数组形式)
|
||||
users := database.Select("user", []string{"id", "name", "email"})
|
||||
|
||||
// 单条件查询
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 多条件查询(自动用 AND 连接)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
})
|
||||
// 生成: WHERE `status`=? AND `age`>?
|
||||
```
|
||||
|
||||
#### 复杂条件查询
|
||||
|
||||
```go
|
||||
// 简化语法:多条件自动用 AND 连接
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"name[~]": "张",
|
||||
})
|
||||
// 生成: WHERE `status`=? AND `age`>? AND `name` LIKE ?
|
||||
|
||||
// 显式 AND 条件(与上面等效)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"AND": common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"name[~]": "张",
|
||||
},
|
||||
})
|
||||
|
||||
// OR 条件
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"OR": common.Map{
|
||||
"status": 1,
|
||||
"type": 2,
|
||||
},
|
||||
})
|
||||
|
||||
// 混合条件(嵌套 AND/OR)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"AND": common.Map{
|
||||
"status": 1,
|
||||
"OR": common.Map{
|
||||
"age[<]": 30,
|
||||
"level[>]": 5,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 带 ORDER BY、LIMIT 等特殊条件(关键字支持大小写)
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
"ORDER": "id DESC", // 或 "order": "id DESC"
|
||||
"LIMIT": 10, // 或 "limit": 10
|
||||
})
|
||||
|
||||
// 带多个特殊条件
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"OR": common.Map{
|
||||
"level": "vip",
|
||||
"balance[>]": 1000,
|
||||
},
|
||||
"ORDER": []string{"created_time DESC", "id ASC"},
|
||||
"GROUP": "department",
|
||||
"LIMIT": []int{0, 20}, // offset 0, limit 20
|
||||
})
|
||||
|
||||
// 使用 HAVING 过滤分组结果
|
||||
users := database.Select("user", "dept_id, COUNT(*) as cnt", common.Map{
|
||||
"GROUP": "dept_id",
|
||||
"HAVING": common.Map{
|
||||
"cnt[>]": 5,
|
||||
},
|
||||
})
|
||||
|
||||
// 使用独立 OFFSET
|
||||
users := database.Select("user", "*", common.Map{
|
||||
"status": 1,
|
||||
"LIMIT": 10,
|
||||
"OFFSET": 20,
|
||||
})
|
||||
```
|
||||
|
||||
### 获取单条记录(Get)
|
||||
|
||||
```go
|
||||
// 获取单个用户
|
||||
user := database.Get("user", "*", common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 获取指定字段
|
||||
user := database.Get("user", "id,name,email", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
### 插入(Insert)
|
||||
|
||||
```go
|
||||
// 基本插入
|
||||
id := database.Insert("user", common.Map{
|
||||
"name": "张三",
|
||||
"email": "zhangsan@example.com",
|
||||
"age": 25,
|
||||
"status": 1,
|
||||
"created_time[#]": "NOW()", // [#]表示直接插入SQL函数
|
||||
})
|
||||
|
||||
// 返回插入的ID
|
||||
fmt.Println("插入的用户ID:", id)
|
||||
```
|
||||
|
||||
### 批量插入(BatchInsert)
|
||||
|
||||
```go
|
||||
// 批量插入多条记录(使用 []Map 格式,更直观)
|
||||
affected := database.BatchInsert("user", []common.Map{
|
||||
{"name": "张三", "email": "zhang@example.com", "age": 25},
|
||||
{"name": "李四", "email": "li@example.com", "age": 30},
|
||||
{"name": "王五", "email": "wang@example.com", "age": 28},
|
||||
})
|
||||
// 生成: INSERT INTO `user` (`age`, `email`, `name`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)
|
||||
|
||||
fmt.Printf("批量插入 %d 条记录\n", affected)
|
||||
|
||||
// 支持 [#] 标记直接插入 SQL 表达式
|
||||
affected := database.BatchInsert("log", []common.Map{
|
||||
{"user_id": 1, "action": "login", "created_time[#]": "NOW()"},
|
||||
{"user_id": 2, "action": "logout", "created_time[#]": "NOW()"},
|
||||
})
|
||||
```
|
||||
|
||||
### 更新(Update)
|
||||
|
||||
```go
|
||||
// 基本更新
|
||||
affected := database.Update("user", common.Map{
|
||||
"name": "李四",
|
||||
"email": "lisi@example.com",
|
||||
"updated_time[#]": "NOW()",
|
||||
}, common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 条件更新(多条件自动 AND 连接)
|
||||
affected := database.Update("user", common.Map{
|
||||
"status": 0,
|
||||
}, common.Map{
|
||||
"age[<]": 18,
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
fmt.Println("更新的记录数:", affected)
|
||||
```
|
||||
|
||||
### Upsert操作
|
||||
|
||||
Upsert(插入或更新):如果记录存在则更新,不存在则插入。
|
||||
|
||||
```go
|
||||
// Upsert 操作(使用 Slice 格式)
|
||||
affected := database.Upsert("user",
|
||||
common.Map{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"email": "zhang@example.com",
|
||||
"login_count": 1,
|
||||
},
|
||||
common.Slice{"id"}, // 唯一键(用于冲突检测)
|
||||
common.Slice{"name", "email", "login_count"}, // 冲突时更新的字段
|
||||
)
|
||||
|
||||
// MySQL 生成:
|
||||
// INSERT INTO user (id,name,email,login_count) VALUES (?,?,?,?)
|
||||
// ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email), login_count=VALUES(login_count)
|
||||
|
||||
// PostgreSQL 生成:
|
||||
// INSERT INTO "user" (id,name,email,login_count) VALUES ($1,$2,$3,$4)
|
||||
// ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email, login_count=EXCLUDED.login_count
|
||||
|
||||
// 使用 [#] 标记直接 SQL 更新
|
||||
affected := database.Upsert("user",
|
||||
common.Map{
|
||||
"id": 1,
|
||||
"name": "张三",
|
||||
"login_count[#]": "login_count + 1", // 直接 SQL 表达式
|
||||
},
|
||||
common.Slice{"id"},
|
||||
common.Slice{"name", "login_count"},
|
||||
)
|
||||
|
||||
// 也支持可变参数形式
|
||||
affected := database.Upsert("user",
|
||||
common.Map{"id": 1, "name": "张三"},
|
||||
common.Slice{"id"},
|
||||
"name", "email", // 可变参数
|
||||
)
|
||||
```
|
||||
|
||||
### 删除(Delete)
|
||||
|
||||
```go
|
||||
// 根据ID删除
|
||||
affected := database.Delete("user", common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
// 条件删除(多条件自动 AND 连接)
|
||||
affected := database.Delete("user", common.Map{
|
||||
"status": 0,
|
||||
"created_time[<]": "2023-01-01",
|
||||
})
|
||||
|
||||
fmt.Println("删除的记录数:", affected)
|
||||
```
|
||||
|
||||
## 链式查询构建器
|
||||
|
||||
HoTimeDB提供了链式查询构建器,让查询更加直观:
|
||||
|
||||
```go
|
||||
// 基本链式查询
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
And("age[>]", 18).
|
||||
Order("created_time DESC").
|
||||
Limit(10, 20). // offset, limit
|
||||
Select()
|
||||
|
||||
// 链式获取单条记录
|
||||
user := database.Table("user").
|
||||
Where("id", 1).
|
||||
Get()
|
||||
|
||||
// 链式更新
|
||||
affected := database.Table("user").
|
||||
Where("id", 1).
|
||||
Update(common.Map{
|
||||
"name": "新名称",
|
||||
"updated_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
// 链式删除
|
||||
affected := database.Table("user").
|
||||
Where("status", 0).
|
||||
Delete()
|
||||
|
||||
// 链式统计
|
||||
count := database.Table("user").
|
||||
Where("status", 1).
|
||||
Count()
|
||||
```
|
||||
|
||||
### 链式条件组合
|
||||
|
||||
```go
|
||||
// 复杂条件组合
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
And("age[>=]", 18).
|
||||
Or(common.Map{
|
||||
"level[>]": 5,
|
||||
"vip": 1,
|
||||
}).
|
||||
Order("created_time DESC", "id ASC").
|
||||
Group("department").
|
||||
Having(common.Map{"COUNT(*).[>]": 5}). // 新增 HAVING 支持
|
||||
Limit(0, 20).
|
||||
Offset(10). // 新增独立 OFFSET 支持
|
||||
Select("id,name,email,age")
|
||||
```
|
||||
|
||||
## 条件查询语法
|
||||
|
||||
HoTimeDB支持丰富的条件查询语法,类似于Medoo:
|
||||
|
||||
### 基本比较
|
||||
|
||||
```go
|
||||
// 等于
|
||||
"id": 1
|
||||
|
||||
// 不等于
|
||||
"id[!]": 1
|
||||
|
||||
// 大于
|
||||
"age[>]": 18
|
||||
|
||||
// 大于等于
|
||||
"age[>=]": 18
|
||||
|
||||
// 小于
|
||||
"age[<]": 60
|
||||
|
||||
// 小于等于
|
||||
"age[<=]": 60
|
||||
```
|
||||
|
||||
### 模糊查询
|
||||
|
||||
```go
|
||||
// LIKE %keyword%
|
||||
"name[~]": "张"
|
||||
|
||||
// LIKE keyword% (右边任意)
|
||||
"name[~!]": "张"
|
||||
|
||||
// LIKE %keyword (左边任意)
|
||||
"name[!~]": "san"
|
||||
|
||||
// 手动LIKE(需要手动添加%)
|
||||
"name[~~]": "%张%"
|
||||
```
|
||||
|
||||
### 区间查询
|
||||
|
||||
```go
|
||||
// BETWEEN
|
||||
"age[<>]": []int{18, 60}
|
||||
|
||||
// NOT BETWEEN
|
||||
"age[><]": []int{18, 25}
|
||||
```
|
||||
|
||||
### IN查询
|
||||
|
||||
```go
|
||||
// IN
|
||||
"id": []int{1, 2, 3, 4, 5}
|
||||
|
||||
// NOT IN
|
||||
"id[!]": []int{1, 2, 3}
|
||||
```
|
||||
|
||||
### NULL查询
|
||||
|
||||
```go
|
||||
// IS NULL
|
||||
"deleted_at": nil
|
||||
|
||||
// IS NOT NULL
|
||||
"deleted_at[!]": nil
|
||||
```
|
||||
|
||||
### 直接SQL
|
||||
|
||||
```go
|
||||
// 直接插入SQL表达式(注意防注入)
|
||||
"created_time[#]": "> DATE_SUB(NOW(), INTERVAL 1 DAY)"
|
||||
|
||||
// 字段直接赋值(不使用参数化查询)
|
||||
"update_time[#]": "NOW()"
|
||||
|
||||
// 直接SQL片段
|
||||
"[##]": "user.status = 1 AND user.level > 0"
|
||||
```
|
||||
|
||||
## JOIN操作
|
||||
|
||||
### 链式JOIN
|
||||
|
||||
```go
|
||||
// LEFT JOIN
|
||||
users := database.Table("user").
|
||||
LeftJoin("profile", "user.id = profile.user_id").
|
||||
LeftJoin("department", "user.dept_id = department.id").
|
||||
Where("user.status", 1).
|
||||
Select("user.*, profile.avatar, department.name AS dept_name")
|
||||
|
||||
// RIGHT JOIN
|
||||
users := database.Table("user").
|
||||
RightJoin("order", "user.id = order.user_id").
|
||||
Select()
|
||||
|
||||
// INNER JOIN
|
||||
users := database.Table("user").
|
||||
InnerJoin("profile", "user.id = profile.user_id").
|
||||
Select()
|
||||
|
||||
// FULL JOIN
|
||||
users := database.Table("user").
|
||||
FullJoin("profile", "user.id = profile.user_id").
|
||||
Select()
|
||||
```
|
||||
|
||||
### 传统JOIN语法
|
||||
|
||||
```go
|
||||
users := database.Select("user",
|
||||
common.Slice{
|
||||
common.Map{"[>]profile": "user.id = profile.user_id"},
|
||||
common.Map{"[>]department": "user.dept_id = department.id"},
|
||||
},
|
||||
"user.*, profile.avatar, department.name AS dept_name",
|
||||
common.Map{
|
||||
"user.status": 1,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### JOIN类型说明
|
||||
|
||||
- `[>]`: LEFT JOIN
|
||||
- `[<]`: RIGHT JOIN
|
||||
- `[><]`: INNER JOIN
|
||||
- `[<>]`: FULL JOIN
|
||||
|
||||
## 分页查询
|
||||
|
||||
### 基本分页
|
||||
|
||||
```go
|
||||
// 设置分页:页码3,每页20条
|
||||
users := database.Page(3, 20).PageSelect("user", "*", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 链式分页
|
||||
users := database.Table("user").
|
||||
Where("status", 1).
|
||||
Page(2, 15). // 第2页,每页15条
|
||||
Select()
|
||||
```
|
||||
|
||||
### 分页信息获取
|
||||
|
||||
```go
|
||||
// 获取总数
|
||||
total := database.Count("user", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// 计算分页信息
|
||||
page := 2
|
||||
pageSize := 20
|
||||
offset := (page - 1) * pageSize
|
||||
totalPages := (total + pageSize - 1) / pageSize
|
||||
|
||||
fmt.Printf("总记录数: %d, 总页数: %d, 当前页: %d\n", total, totalPages, page)
|
||||
```
|
||||
|
||||
## 聚合函数
|
||||
|
||||
### 计数
|
||||
|
||||
```go
|
||||
// 总数统计
|
||||
total := database.Count("user")
|
||||
|
||||
// 条件统计
|
||||
activeUsers := database.Count("user", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
|
||||
// JOIN统计
|
||||
count := database.Count("user",
|
||||
common.Slice{
|
||||
common.Map{"[>]profile": "user.id = profile.user_id"},
|
||||
},
|
||||
common.Map{
|
||||
"user.status": 1,
|
||||
"profile.verified": 1,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 求和
|
||||
|
||||
```go
|
||||
// 基本求和
|
||||
totalAmount := database.Sum("order", "amount")
|
||||
|
||||
// 条件求和
|
||||
paidAmount := database.Sum("order", "amount", common.Map{
|
||||
"status": "paid",
|
||||
"created_time[>]": "2023-01-01",
|
||||
})
|
||||
```
|
||||
|
||||
### 平均值
|
||||
|
||||
```go
|
||||
// 基本平均值
|
||||
avgAge := database.Avg("user", "age")
|
||||
|
||||
// 条件平均值
|
||||
avgAge := database.Avg("user", "age", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
### 最大值/最小值
|
||||
|
||||
```go
|
||||
// 最大值
|
||||
maxAge := database.Max("user", "age")
|
||||
|
||||
// 最小值
|
||||
minAge := database.Min("user", "age")
|
||||
|
||||
// 条件最大值
|
||||
maxBalance := database.Max("user", "balance", common.Map{
|
||||
"status": 1,
|
||||
})
|
||||
```
|
||||
|
||||
## 事务处理
|
||||
|
||||
```go
|
||||
// 事务操作
|
||||
success := database.Action(func(tx db.HoTimeDB) bool {
|
||||
// 在事务中执行多个操作
|
||||
|
||||
// 扣减用户余额
|
||||
affected1 := tx.Update("user", common.Map{
|
||||
"balance[#]": "balance - 100",
|
||||
}, common.Map{
|
||||
"id": 1,
|
||||
})
|
||||
|
||||
if affected1 == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
orderId := tx.Insert("order", common.Map{
|
||||
"user_id": 1,
|
||||
"amount": 100,
|
||||
"status": "paid",
|
||||
"created_time[#]": "NOW()",
|
||||
})
|
||||
|
||||
if orderId == 0 {
|
||||
return false // 回滚
|
||||
}
|
||||
|
||||
return true // 提交
|
||||
})
|
||||
|
||||
if success {
|
||||
fmt.Println("事务执行成功")
|
||||
} else {
|
||||
fmt.Println("事务回滚")
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存机制
|
||||
|
||||
HoTimeDB集成了缓存功能,可以自动缓存查询结果:
|
||||
|
||||
### 缓存配置
|
||||
|
||||
```go
|
||||
import "code.hoteas.com/golang/hotime/cache"
|
||||
|
||||
// 设置缓存
|
||||
database.HoTimeCache = &cache.HoTimeCache{
|
||||
// 缓存配置
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存行为
|
||||
|
||||
- 查询操作会自动检查缓存
|
||||
- 增删改操作会自动清除相关缓存
|
||||
- 缓存键格式:`表名:查询MD5`
|
||||
- `cached`表不会被缓存
|
||||
|
||||
### 缓存清理
|
||||
|
||||
```go
|
||||
// 手动清除表缓存
|
||||
database.HoTimeCache.Db("user*", nil) // 清除user表所有缓存
|
||||
```
|
||||
|
||||
## PostgreSQL支持
|
||||
|
||||
HoTimeDB 支持 PostgreSQL 数据库,自动处理语法差异。
|
||||
|
||||
### PostgreSQL 配置
|
||||
|
||||
```go
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/db"
|
||||
_ "github.com/lib/pq" // PostgreSQL 驱动
|
||||
)
|
||||
|
||||
database := &db.HoTimeDB{
|
||||
Type: "postgres", // 设置数据库类型为 postgres
|
||||
Prefix: "app_",
|
||||
}
|
||||
|
||||
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
|
||||
dsn := "host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable"
|
||||
master, _ = sql.Open("postgres", dsn)
|
||||
return master, master
|
||||
})
|
||||
```
|
||||
|
||||
### 主要差异
|
||||
|
||||
| 特性 | MySQL | PostgreSQL |
|
||||
|------|-------|------------|
|
||||
| 标识符引号 | \`name\` | "name" |
|
||||
| 占位符 | ? | $1, $2, $3... |
|
||||
| Upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE |
|
||||
|
||||
所有这些差异由框架自动处理,无需手动调整代码。
|
||||
|
||||
## 高级特性
|
||||
|
||||
### 调试模式
|
||||
|
||||
```go
|
||||
// 设置调试模式
|
||||
database.Mode = 2
|
||||
|
||||
// 查看最后执行的SQL
|
||||
fmt.Println("最后的SQL:", database.LastQuery)
|
||||
fmt.Println("参数:", database.LastData)
|
||||
fmt.Println("错误:", database.LastErr.GetError())
|
||||
```
|
||||
|
||||
### 主从分离
|
||||
|
||||
```go
|
||||
func createConnection() (master, slave *sql.DB) {
|
||||
// 主库连接
|
||||
master, _ = sql.Open("mysql", "user:password@tcp(master:3306)/database")
|
||||
|
||||
// 从库连接
|
||||
slave, _ = sql.Open("mysql", "user:password@tcp(slave:3306)/database")
|
||||
|
||||
return master, slave
|
||||
}
|
||||
|
||||
database.SetConnect(createConnection)
|
||||
// 查询会自动使用从库,增删改使用主库
|
||||
```
|
||||
|
||||
### 原生SQL执行
|
||||
|
||||
```go
|
||||
// 执行查询SQL
|
||||
results := database.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1)
|
||||
|
||||
// 执行更新SQL
|
||||
result, err := database.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1)
|
||||
if err.GetError() == nil {
|
||||
affected, _ := result.RowsAffected()
|
||||
fmt.Println("影响行数:", affected)
|
||||
}
|
||||
```
|
||||
|
||||
## 特殊语法详解
|
||||
|
||||
### 条件标记符说明
|
||||
|
||||
| 标记符 | 功能 | 示例 | 生成SQL |
|
||||
|--------|------|------|---------|
|
||||
| `[>]` | 大于 | `"age[>]": 18` | `age > 18` |
|
||||
| `[<]` | 小于 | `"age[<]": 60` | `age < 60` |
|
||||
| `[>=]` | 大于等于 | `"age[>=]": 18` | `age >= 18` |
|
||||
| `[<=]` | 小于等于 | `"age[<=]": 60` | `age <= 60` |
|
||||
| `[!]` | 不等于/NOT IN | `"id[!]": 1` | `id != 1` |
|
||||
| `[~]` | LIKE模糊查询 | `"name[~]": "张"` | `name LIKE '%张%'` |
|
||||
| `[!~]` | 左模糊 | `"name[!~]": "张"` | `name LIKE '%张'` |
|
||||
| `[~!]` | 右模糊 | `"name[~!]": "张"` | `name LIKE '张%'` |
|
||||
| `[~~]` | 手动LIKE | `"name[~~]": "%张%"` | `name LIKE '%张%'` |
|
||||
| `[<>]` | BETWEEN | `"age[<>]": [18,60]` | `age BETWEEN 18 AND 60` |
|
||||
| `[><]` | NOT BETWEEN | `"age[><]": [18,25]` | `age NOT BETWEEN 18 AND 25` |
|
||||
| `[#]` | 直接SQL | `"time[#]": "NOW()"` | `time = NOW()` |
|
||||
| `[##]` | SQL片段 | `"[##]": "a > b"` | `a > b` |
|
||||
| `[#!]` | 不等于直接SQL | `"status[#!]": "1"` | `status != 1` |
|
||||
| `[!#]` | 不等于直接SQL | `"status[!#]": "1"` | `status != 1` |
|
||||
|
||||
### 特殊关键字(支持大小写)
|
||||
|
||||
| 关键字 | 功能 | 示例 |
|
||||
|--------|------|------|
|
||||
| `ORDER` / `order` | 排序 | `"ORDER": "id DESC"` |
|
||||
| `GROUP` / `group` | 分组 | `"GROUP": "dept_id"` |
|
||||
| `LIMIT` / `limit` | 限制 | `"LIMIT": 10` |
|
||||
| `OFFSET` / `offset` | 偏移 | `"OFFSET": 20` |
|
||||
| `HAVING` / `having` | 分组过滤 | `"HAVING": Map{"cnt[>]": 5}` |
|
||||
| `DISTINCT` / `distinct` | 去重 | 在 SELECT 中使用 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 多条件查询需要用 AND 包装吗?
|
||||
A1: 不再需要!现在多条件会自动用 AND 连接。当然,使用 `AND` 包装仍然有效(向后兼容)。
|
||||
|
||||
### Q2: 如何处理事务中的错误?
|
||||
A2: 在 `Action` 函数中返回 `false` 即可触发回滚,所有操作都会被撤销。
|
||||
|
||||
### Q3: 缓存何时会被清除?
|
||||
A3: 执行 `Insert`、`Update`、`Delete`、`Upsert`、`BatchInsert` 操作时会自动清除对应表的缓存。
|
||||
|
||||
### Q4: 如何执行复杂的原生SQL?
|
||||
A4: 使用 `Query` 方法执行查询,使用 `Exec` 方法执行更新操作。
|
||||
|
||||
### Q5: 主从分离如何工作?
|
||||
A5: 查询操作自动使用从库(如果配置了),增删改操作使用主库。
|
||||
|
||||
### Q6: 如何处理NULL值?
|
||||
A6: 使用 `nil` 作为值,查询时使用 `"field": nil` 表示 `IS NULL`。
|
||||
|
||||
### Q7: PostgreSQL 和 MySQL 语法有区别吗?
|
||||
A7: 框架会自动处理差异(占位符、引号等),代码无需修改。
|
||||
|
||||
---
|
||||
|
||||
*文档版本: 2.0*
|
||||
*最后更新: 2026年1月*
|
||||
|
||||
> 本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,但根据Golang语言特性进行了适配和优化。
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
# HoTimeDB ORM 文档集合
|
||||
|
||||
这是HoTimeDB ORM框架的完整文档集合,包含使用说明、API参考、示例代码和测试数据。
|
||||
|
||||
## ⚠️ 重要更新说明
|
||||
|
||||
**语法修正通知**:经过对源码的深入分析,发现HoTimeDB的条件查询语法有特定规则:
|
||||
- ✅ **单个条件**:可以直接写在Map中
|
||||
- ⚠️ **多个条件**:必须使用`AND`或`OR`包装
|
||||
- 📝 所有文档和示例代码已按正确语法更新
|
||||
|
||||
## 📚 文档列表
|
||||
|
||||
### 1. [HoTimeDB_使用说明.md](./HoTimeDB_使用说明.md)
|
||||
**完整使用说明书** - 详细的功能介绍和使用指南
|
||||
- 🚀 快速开始
|
||||
- ⚙️ 数据库配置
|
||||
- 🔧 基本操作 (CRUD)
|
||||
- 🔗 链式查询构建器
|
||||
- 🔍 条件查询语法
|
||||
- 🔄 JOIN操作
|
||||
- 📄 分页查询
|
||||
- 📊 聚合函数
|
||||
- 🔐 事务处理
|
||||
- 💾 缓存机制
|
||||
- ⚡ 高级特性
|
||||
|
||||
### 2. [HoTimeDB_API参考.md](./HoTimeDB_API参考.md)
|
||||
**快速API参考手册** - 开发时的速查手册
|
||||
- 📖 基本方法
|
||||
- 🔧 CRUD操作
|
||||
- 📊 聚合函数
|
||||
- 📄 分页查询
|
||||
- 🔍 条件语法参考
|
||||
- 🔗 JOIN语法
|
||||
- 🔐 事务处理
|
||||
- 🛠️ 工具方法
|
||||
|
||||
### 3. [示例代码文件](../examples/hotimedb_examples.go)
|
||||
**完整示例代码集合** - 可运行的实际应用示例(语法已修正)
|
||||
- 🏗️ 基本初始化和配置
|
||||
- 📝 基本CRUD操作
|
||||
- 🔗 链式查询操作
|
||||
- 🤝 JOIN查询操作
|
||||
- 🔍 条件查询语法
|
||||
- 📄 分页查询
|
||||
- 📊 聚合函数查询
|
||||
- 🔐 事务处理
|
||||
- 💾 缓存机制
|
||||
- 🔧 原生SQL执行
|
||||
- 🚨 错误处理和调试
|
||||
- ⚡ 性能优化技巧
|
||||
- 🎯 完整应用示例
|
||||
|
||||
### 4. [test_tables.sql](./test_tables.sql)
|
||||
**测试数据库结构** - 快速搭建测试环境
|
||||
- 🏗️ 完整的表结构定义
|
||||
- 📊 测试数据插入
|
||||
- 🔍 索引优化
|
||||
- 👁️ 视图示例
|
||||
- 🔧 存储过程示例
|
||||
|
||||
## 🎯 核心特性
|
||||
|
||||
### 🌟 主要优势
|
||||
- **类Medoo语法**: 参考PHP Medoo设计,语法简洁易懂
|
||||
- **链式查询**: 支持流畅的链式查询构建器
|
||||
- **条件丰富**: 支持丰富的条件查询语法
|
||||
- **事务支持**: 完整的事务处理机制
|
||||
- **缓存集成**: 内置查询结果缓存
|
||||
- **读写分离**: 支持主从数据库配置
|
||||
- **类型安全**: 基于Golang的强类型系统
|
||||
|
||||
### 🔧 支持的数据库
|
||||
- ✅ MySQL
|
||||
- ✅ SQLite
|
||||
- ✅ 其他标准SQL数据库
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
```bash
|
||||
go mod init your-project
|
||||
go get github.com/go-sql-driver/mysql
|
||||
go get github.com/sirupsen/logrus
|
||||
```
|
||||
|
||||
### 2. 创建测试数据库
|
||||
```bash
|
||||
mysql -u root -p < test_tables.sql
|
||||
```
|
||||
|
||||
### 3. 基本使用
|
||||
```go
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/db"
|
||||
"code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
// 初始化数据库
|
||||
database := &db.HoTimeDB{
|
||||
Prefix: "app_",
|
||||
Mode: 2, // 开发模式
|
||||
}
|
||||
|
||||
database.SetConnect(func() (master, slave *sql.DB) {
|
||||
master, _ = sql.Open("mysql", "user:pass@tcp(localhost:3306)/dbname")
|
||||
return master, master
|
||||
})
|
||||
|
||||
// 链式查询(链式语法支持单独Where然后用And添加条件)
|
||||
users := database.Table("user").
|
||||
Where("status", 1). // 链式中可以单独Where
|
||||
And("age[>]", 18). // 用And添加更多条件
|
||||
Order("created_time DESC").
|
||||
Limit(0, 10).
|
||||
Select("id,name,email")
|
||||
|
||||
// 或者使用传统语法(多个条件必须用AND包装)
|
||||
users2 := database.Select("user", "id,name,email", common.Map{
|
||||
"AND": common.Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
"ORDER": "created_time DESC",
|
||||
"LIMIT": []int{0, 10},
|
||||
})
|
||||
```
|
||||
|
||||
## ⚠️ 重要语法规则
|
||||
|
||||
**条件查询语法规则:**
|
||||
- ✅ **单个条件**:可以直接写在Map中
|
||||
- ✅ **多个条件**:必须使用`AND`或`OR`包装
|
||||
- ✅ **特殊参数**:`ORDER`、`GROUP`、`LIMIT`与条件同级
|
||||
|
||||
```go
|
||||
// ✅ 正确:单个条件
|
||||
Map{"status": 1}
|
||||
|
||||
// ✅ 正确:多个条件用AND包装
|
||||
Map{
|
||||
"AND": Map{
|
||||
"status": 1,
|
||||
"age[>]": 18,
|
||||
},
|
||||
"ORDER": "id DESC",
|
||||
}
|
||||
|
||||
// ❌ 错误:多个条件不用AND包装
|
||||
Map{
|
||||
"status": 1,
|
||||
"age[>]": 18, // 不支持!
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 条件查询语法速查
|
||||
|
||||
| 语法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"field": value` | `field = ?` | 等于 |
|
||||
| `"field[!]": value` | `field != ?` | 不等于 |
|
||||
| `"field[>]": value` | `field > ?` | 大于 |
|
||||
| `"field[>=]": value` | `field >= ?` | 大于等于 |
|
||||
| `"field[<]": value` | `field < ?` | 小于 |
|
||||
| `"field[<=]": value` | `field <= ?` | 小于等于 |
|
||||
| `"field[~]": "keyword"` | `field LIKE '%keyword%'` | 包含 |
|
||||
| `"field[<>]": [min, max]` | `field BETWEEN ? AND ?` | 区间内 |
|
||||
| `"field": [v1, v2, v3]` | `field IN (?, ?, ?)` | 在集合中 |
|
||||
| `"field": nil` | `field IS NULL` | 为空 |
|
||||
| `"field[#]": "NOW()"` | `field = NOW()` | 直接SQL |
|
||||
|
||||
## 🔗 JOIN语法速查
|
||||
|
||||
| 语法 | SQL | 说明 |
|
||||
|------|-----|------|
|
||||
| `"[>]table"` | `LEFT JOIN` | 左连接 |
|
||||
| `"[<]table"` | `RIGHT JOIN` | 右连接 |
|
||||
| `"[><]table"` | `INNER JOIN` | 内连接 |
|
||||
| `"[<>]table"` | `FULL JOIN` | 全连接 |
|
||||
|
||||
## 🛠️ 链式方法速查
|
||||
|
||||
```go
|
||||
db.Table("table") // 指定表名
|
||||
.Where(key, value) // WHERE条件
|
||||
.And(key, value) // AND条件
|
||||
.Or(map) // OR条件
|
||||
.LeftJoin(table, on) // LEFT JOIN
|
||||
.Order(fields...) // ORDER BY
|
||||
.Group(fields...) // GROUP BY
|
||||
.Limit(offset, limit) // LIMIT
|
||||
.Page(page, pageSize) // 分页
|
||||
.Select(fields...) // 查询
|
||||
.Get(fields...) // 获取单条
|
||||
.Count() // 计数
|
||||
.Update(data) // 更新
|
||||
.Delete() // 删除
|
||||
```
|
||||
|
||||
## ⚡ 性能优化建议
|
||||
|
||||
### 🔍 查询优化
|
||||
- 使用合适的索引字段作为查询条件
|
||||
- IN查询会自动优化为BETWEEN(连续数字)
|
||||
- 避免SELECT *,指定需要的字段
|
||||
- 合理使用LIMIT限制结果集大小
|
||||
|
||||
### 💾 缓存使用
|
||||
- 查询结果会自动缓存
|
||||
- 增删改操作会自动清除缓存
|
||||
- `cached`表不参与缓存
|
||||
|
||||
### 🔐 事务处理
|
||||
- 批量操作使用事务提高性能
|
||||
- 事务中避免长时间操作
|
||||
- 合理设置事务隔离级别
|
||||
|
||||
## 🚨 注意事项
|
||||
|
||||
### 🔒 安全相关
|
||||
- 使用参数化查询防止SQL注入
|
||||
- `[#]`语法需要注意防止注入
|
||||
- 敏感数据加密存储
|
||||
|
||||
### 🎯 最佳实践
|
||||
- 开发时设置`Mode = 2`便于调试
|
||||
- 生产环境设置`Mode = 0`
|
||||
- 合理设置表前缀
|
||||
- 定期检查慢查询日志
|
||||
|
||||
## 🤝 与PHP Medoo的差异
|
||||
|
||||
1. **类型系统**: 使用`common.Map`和`common.Slice`
|
||||
2. **错误处理**: Golang风格的错误处理
|
||||
3. **链式调用**: 提供更丰富的链式API
|
||||
4. **缓存集成**: 内置缓存功能
|
||||
5. **并发安全**: 需要注意并发使用
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
- 📧 查看源码:`hotimedb.go`
|
||||
- 📖 参考文档:本目录下的各个文档文件
|
||||
- 🔧 示例代码:运行`HoTimeDB_示例代码.go`中的示例
|
||||
|
||||
---
|
||||
|
||||
**HoTimeDB ORM框架 - 让数据库操作更简单!** 🎉
|
||||
|
||||
> 本文档基于HoTimeDB源码分析生成,参考了PHP Medoo的设计理念,并根据Golang语言特性进行了优化。
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// Count 计数
|
||||
func (that *HoTimeDB) Count(table string, qu ...interface{}) int {
|
||||
var 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...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["COUNT(*)"])
|
||||
count, _ := StrToInt(res)
|
||||
return count
|
||||
}
|
||||
|
||||
// Sum 求和
|
||||
func (that *HoTimeDB) Sum(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "SUM("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "SUM("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["SUM("+column+")"])
|
||||
sum := ObjToFloat64(res)
|
||||
return sum
|
||||
}
|
||||
|
||||
// Avg 平均值
|
||||
func (that *HoTimeDB) Avg(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "AVG("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "AVG("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["AVG("+column+")"])
|
||||
avg := ObjToFloat64(res)
|
||||
return avg
|
||||
}
|
||||
|
||||
// Max 最大值
|
||||
func (that *HoTimeDB) Max(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "MAX("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "MAX("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["MAX("+column+")"])
|
||||
max := ObjToFloat64(res)
|
||||
return max
|
||||
}
|
||||
|
||||
// Min 最小值
|
||||
func (that *HoTimeDB) Min(table string, column string, qu ...interface{}) float64 {
|
||||
var req = []interface{}{}
|
||||
if len(qu) == 2 {
|
||||
req = append(req, qu[0])
|
||||
req = append(req, "MIN("+column+")")
|
||||
req = append(req, qu[1])
|
||||
} else {
|
||||
req = append(req, "MIN("+column+")")
|
||||
req = append(req, qu...)
|
||||
}
|
||||
|
||||
data := that.Select(table, req...)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := ObjToStr(data[0]["MIN("+column+")"])
|
||||
min := ObjToFloat64(res)
|
||||
return min
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// backupSave 保存备份
|
||||
// path: 备份文件路径
|
||||
// tt: 表名
|
||||
// code: 备份类型 0=全部, 1=仅数据, 2=仅DDL
|
||||
func (that *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 += that.backupDdl(tt)
|
||||
}
|
||||
|
||||
if code == 0 || code == 1 {
|
||||
str += "insert into `" + tt + "`\r\n\r\n("
|
||||
str += that.backupCol(tt)
|
||||
}
|
||||
|
||||
_, _ = fd.Write([]byte(str))
|
||||
}
|
||||
|
||||
// backupDdl 备份表结构(DDL)
|
||||
func (that *HoTimeDB) backupDdl(tt string) string {
|
||||
data := that.Query("show create table " + tt)
|
||||
if len(data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n"
|
||||
}
|
||||
|
||||
// backupCol 备份表数据
|
||||
func (that *HoTimeDB) backupCol(tt string) string {
|
||||
str := ""
|
||||
data := that.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
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// HotimeDBBuilder 链式查询构建器
|
||||
type HotimeDBBuilder struct {
|
||||
HoTimeDB *HoTimeDB
|
||||
table string
|
||||
selects []interface{}
|
||||
join Slice
|
||||
where Map
|
||||
lastWhere Map
|
||||
page int
|
||||
pageRow int
|
||||
}
|
||||
|
||||
// Table 创建链式查询构建器
|
||||
func (that *HoTimeDB) Table(table string) *HotimeDBBuilder {
|
||||
return &HotimeDBBuilder{HoTimeDB: that, table: table, where: Map{}}
|
||||
}
|
||||
|
||||
// Get 获取单条记录
|
||||
func (that *HotimeDBBuilder) Get(qu ...interface{}) Map {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
var args []interface{}
|
||||
if len(that.join) > 0 {
|
||||
// 有 JOIN 时:join, fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, that.join, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, that.join, "*", that.where)
|
||||
}
|
||||
} else {
|
||||
// 无 JOIN 时:fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, "*", that.where)
|
||||
}
|
||||
}
|
||||
return that.HoTimeDB.Get(that.table, args...)
|
||||
}
|
||||
|
||||
// Count 统计数量
|
||||
func (that *HotimeDBBuilder) Count() int {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
if len(that.join) > 0 {
|
||||
return that.HoTimeDB.Count(that.table, that.join, that.where)
|
||||
}
|
||||
return that.HoTimeDB.Count(that.table, that.where)
|
||||
}
|
||||
|
||||
// Page 设置分页
|
||||
func (that *HotimeDBBuilder) Page(page, pageRow int) *HotimeDBBuilder {
|
||||
that.page = page
|
||||
that.pageRow = pageRow
|
||||
return that
|
||||
}
|
||||
|
||||
// Select 查询多条记录
|
||||
func (that *HotimeDBBuilder) Select(qu ...interface{}) []Map {
|
||||
// 构建参数:根据是否有 JOIN 来决定参数结构
|
||||
var args []interface{}
|
||||
if len(that.join) > 0 {
|
||||
// 有 JOIN 时:join, fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, that.join, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, that.join, "*", that.where)
|
||||
}
|
||||
} else {
|
||||
// 无 JOIN 时:fields, where
|
||||
if len(qu) > 0 {
|
||||
args = append(args, qu[0], that.where)
|
||||
} else {
|
||||
args = append(args, "*", that.where)
|
||||
}
|
||||
}
|
||||
|
||||
if that.page != 0 {
|
||||
return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, args...)
|
||||
}
|
||||
return that.HoTimeDB.Select(that.table, args...)
|
||||
}
|
||||
|
||||
// Update 更新记录
|
||||
func (that *HotimeDBBuilder) Update(qu ...interface{}) int64 {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return 0
|
||||
}
|
||||
data := Map{}
|
||||
if lth == 1 {
|
||||
data = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
for k := 1; k < lth; k++ {
|
||||
data[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
return that.HoTimeDB.Update(that.table, data, that.where)
|
||||
}
|
||||
|
||||
// Delete 删除记录
|
||||
func (that *HotimeDBBuilder) Delete() int64 {
|
||||
return that.HoTimeDB.Delete(that.table, that.where)
|
||||
}
|
||||
|
||||
// LeftJoin 左连接
|
||||
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[>]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// RightJoin 右连接
|
||||
func (that *HotimeDBBuilder) RightJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[<]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// InnerJoin 内连接
|
||||
func (that *HotimeDBBuilder) InnerJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[><]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// FullJoin 全连接
|
||||
func (that *HotimeDBBuilder) FullJoin(table, joinStr string) *HotimeDBBuilder {
|
||||
that.Join(Map{"[<>]" + table: joinStr})
|
||||
return that
|
||||
}
|
||||
|
||||
// Join 通用连接
|
||||
func (that *HotimeDBBuilder) Join(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
data := Map{}
|
||||
if lth == 1 {
|
||||
data = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
for k := 1; k < lth; k++ {
|
||||
data[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if that.join == nil {
|
||||
that.join = Slice{}
|
||||
}
|
||||
if data == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
that.join = append(that.join, data)
|
||||
return that
|
||||
}
|
||||
|
||||
// And 添加 AND 条件
|
||||
func (that *HotimeDBBuilder) And(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["AND"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"AND": where}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// Or 添加 OR 条件
|
||||
func (that *HotimeDBBuilder) Or(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["OR"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"Or": where}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// Where 设置 WHERE 条件
|
||||
func (that *HotimeDBBuilder) Where(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var where Map
|
||||
if lth == 1 {
|
||||
where = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
where = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
where[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
|
||||
if where == nil {
|
||||
return that
|
||||
}
|
||||
|
||||
if that.lastWhere != nil {
|
||||
that.lastWhere["AND"] = where
|
||||
that.lastWhere = where
|
||||
return that
|
||||
}
|
||||
that.lastWhere = where
|
||||
that.where = Map{"AND": that.lastWhere}
|
||||
|
||||
return that
|
||||
}
|
||||
|
||||
// From 设置表名
|
||||
func (that *HotimeDBBuilder) From(table string) *HotimeDBBuilder {
|
||||
that.table = table
|
||||
return that
|
||||
}
|
||||
|
||||
// Order 设置排序
|
||||
func (that *HotimeDBBuilder) Order(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["ORDER"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Limit 设置限制
|
||||
func (that *HotimeDBBuilder) Limit(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["LIMIT"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Group 设置分组
|
||||
func (that *HotimeDBBuilder) Group(qu ...interface{}) *HotimeDBBuilder {
|
||||
that.where["GROUP"] = ObjToSlice(qu)
|
||||
return that
|
||||
}
|
||||
|
||||
// Having 设置 HAVING 条件
|
||||
func (that *HotimeDBBuilder) Having(qu ...interface{}) *HotimeDBBuilder {
|
||||
lth := len(qu)
|
||||
if lth == 0 {
|
||||
return that
|
||||
}
|
||||
var having Map
|
||||
if lth == 1 {
|
||||
having = ObjToMap(qu[0])
|
||||
}
|
||||
if lth > 1 {
|
||||
having = Map{}
|
||||
for k := 1; k < lth; k++ {
|
||||
having[ObjToStr(qu[k-1])] = qu[k]
|
||||
k++
|
||||
}
|
||||
}
|
||||
that.where["HAVING"] = having
|
||||
return that
|
||||
}
|
||||
|
||||
// Offset 设置偏移量
|
||||
func (that *HotimeDBBuilder) Offset(offset int) *HotimeDBBuilder {
|
||||
that.where["OFFSET"] = offset
|
||||
return that
|
||||
}
|
||||
+660
@@ -0,0 +1,660 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
)
|
||||
|
||||
// Page 设置分页参数
|
||||
// page: 页码(从1开始)
|
||||
// pageRow: 每页数量
|
||||
func (that *HoTimeDB) Page(page, pageRow int) *HoTimeDB {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageRow < 1 {
|
||||
pageRow = 10
|
||||
}
|
||||
offset := (page - 1) * pageRow
|
||||
|
||||
that.limitMu.Lock()
|
||||
that.limit = Slice{offset, pageRow}
|
||||
that.limitMu.Unlock()
|
||||
return that
|
||||
}
|
||||
|
||||
// PageSelect 分页查询
|
||||
func (that *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map {
|
||||
that.limitMu.Lock()
|
||||
limit := that.limit
|
||||
that.limit = nil // 使用后清空,避免影响下次调用
|
||||
that.limitMu.Unlock()
|
||||
|
||||
if limit == nil {
|
||||
return that.Select(table, qu...)
|
||||
}
|
||||
|
||||
// 根据参数数量处理 LIMIT 注入
|
||||
switch len(qu) {
|
||||
case 0:
|
||||
// PageSelect("user") -> 只有表名,添加 LIMIT
|
||||
qu = append(qu, "*", Map{"LIMIT": limit})
|
||||
case 1:
|
||||
// PageSelect("user", "*") 或 PageSelect("user", Map{...})
|
||||
if reflect.ValueOf(qu[0]).Kind() == reflect.Map {
|
||||
// 是 where 条件
|
||||
temp := DeepCopyMap(qu[0]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[0] = temp
|
||||
} else {
|
||||
// 是字段选择
|
||||
qu = append(qu, Map{"LIMIT": limit})
|
||||
}
|
||||
case 2:
|
||||
// PageSelect("user", "*", Map{...}) 或 PageSelect("user", joinSlice, "*")
|
||||
if reflect.ValueOf(qu[1]).Kind() == reflect.Map {
|
||||
temp := DeepCopyMap(qu[1]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[1] = temp
|
||||
} else {
|
||||
// join 模式,需要追加 where
|
||||
qu = append(qu, Map{"LIMIT": limit})
|
||||
}
|
||||
case 3:
|
||||
// PageSelect("user", joinSlice, "*", Map{...})
|
||||
temp := DeepCopyMap(qu[2]).(Map)
|
||||
temp["LIMIT"] = limit
|
||||
qu[2] = temp
|
||||
}
|
||||
|
||||
return that.Select(table, qu...)
|
||||
}
|
||||
|
||||
// Select 查询多条记录
|
||||
func (that *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 {
|
||||
data := ObjToSlice(qu[intQs])
|
||||
for i := 0; i < len(data); i++ {
|
||||
k := data.GetString(i)
|
||||
if strings.Contains(k, " AS ") || strings.Contains(k, ".") {
|
||||
query += " " + k + " "
|
||||
} else {
|
||||
query += " `" + k + "` "
|
||||
}
|
||||
|
||||
if i+1 != len(data) {
|
||||
query = query + ", "
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query += " *"
|
||||
}
|
||||
|
||||
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
|
||||
query += " FROM `" + that.Prefix + table + "` "
|
||||
} else {
|
||||
query += " FROM " + that.Prefix + table + " "
|
||||
}
|
||||
|
||||
if join {
|
||||
query += that.buildJoin(qu[0])
|
||||
}
|
||||
|
||||
if len(qu) > 1 {
|
||||
where = qu[intWhere].(Map)
|
||||
}
|
||||
|
||||
temp, resWhere := that.where(where)
|
||||
|
||||
query += temp + ";"
|
||||
qs = append(qs, resWhere...)
|
||||
md5 := that.md5(query, qs...)
|
||||
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
// 如果缓存有则从缓存取
|
||||
cacheData := that.HoTimeCache.Db(table + ":" + md5)
|
||||
if cacheData != nil && cacheData.Data != nil {
|
||||
return cacheData.ToMapArray()
|
||||
}
|
||||
}
|
||||
|
||||
// 无缓存则数据库取
|
||||
res := that.Query(query, qs...)
|
||||
|
||||
if res == nil {
|
||||
res = []Map{}
|
||||
}
|
||||
|
||||
// 缓存
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+":"+md5, res)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// buildJoin 构建 JOIN 语句
|
||||
func (that *HoTimeDB) buildJoin(joinData interface{}) string {
|
||||
query := ""
|
||||
var testQu = []string{}
|
||||
testQuData := Map{}
|
||||
|
||||
if reflect.ValueOf(joinData).Type().String() == "common.Map" {
|
||||
testQuData = joinData.(Map)
|
||||
for key := range testQuData {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.ValueOf(joinData).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(joinData).Type().String(), "[]") {
|
||||
qu0 := ObjToSlice(joinData)
|
||||
for key := range qu0 {
|
||||
v := qu0.GetMap(key)
|
||||
for k1, v1 := range v {
|
||||
testQu = append(testQu, k1)
|
||||
testQuData[k1] = v1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(testQu)
|
||||
|
||||
for _, k := range testQu {
|
||||
v := testQuData[k]
|
||||
switch Substr(k, 0, 3) {
|
||||
case "[>]":
|
||||
func() {
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
if !strings.Contains(table, " ") {
|
||||
table = "`" + table + "`"
|
||||
}
|
||||
query += " LEFT JOIN " + table + " ON " + v.(string) + " "
|
||||
}()
|
||||
case "[<]":
|
||||
func() {
|
||||
table := Substr(k, 3, len(k)-3)
|
||||
if !strings.Contains(table, " ") {
|
||||
table = "`" + table + "`"
|
||||
}
|
||||
query += " RIGHT JOIN " + table + " ON " + v.(string) + " "
|
||||
}()
|
||||
}
|
||||
switch Substr(k, 0, 4) {
|
||||
case "[<>]":
|
||||
func() {
|
||||
table := Substr(k, 4, len(k)-4)
|
||||
if !strings.Contains(table, " ") {
|
||||
table = "`" + table + "`"
|
||||
}
|
||||
query += " FULL JOIN " + table + " ON " + v.(string) + " "
|
||||
}()
|
||||
case "[><]":
|
||||
func() {
|
||||
table := Substr(k, 4, len(k)-4)
|
||||
if !strings.Contains(table, " ") {
|
||||
table = "`" + table + "`"
|
||||
}
|
||||
query += " INNER JOIN " + table + " ON " + v.(string) + " "
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// Get 获取单条记录
|
||||
func (that *HoTimeDB) Get(table string, qu ...interface{}) Map {
|
||||
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
|
||||
}
|
||||
|
||||
data := that.Select(table, qu...)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data[0]
|
||||
}
|
||||
|
||||
// Insert 插入新数据
|
||||
func (that *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++
|
||||
|
||||
vstr := "?"
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
vstr = ObjToStr(v)
|
||||
if tempLen < lens {
|
||||
queryString += "`" + k + "`,"
|
||||
valueString += vstr + ","
|
||||
} else {
|
||||
queryString += "`" + k + "`) "
|
||||
valueString += vstr + ");"
|
||||
}
|
||||
} else {
|
||||
values = append(values, v)
|
||||
if tempLen < lens {
|
||||
queryString += "`" + k + "`,"
|
||||
valueString += "?,"
|
||||
} else {
|
||||
queryString += "`" + k + "`) "
|
||||
valueString += "?);"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query := "INSERT INTO `" + that.Prefix + table + "` " + queryString + "VALUES" + valueString
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
id := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
id1, err := res.LastInsertId()
|
||||
that.LastErr.SetError(err)
|
||||
id = id1
|
||||
}
|
||||
|
||||
// 如果插入成功,删除缓存
|
||||
if id != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// BatchInsert 批量插入数据
|
||||
// table: 表名
|
||||
// dataList: 数据列表,每个元素是一个 Map
|
||||
// 返回受影响的行数
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// affected := db.BatchInsert("user", []Map{
|
||||
// {"name": "张三", "age": 25, "email": "zhang@example.com"},
|
||||
// {"name": "李四", "age": 30, "email": "li@example.com"},
|
||||
// {"name": "王五", "age": 28, "email": "wang@example.com"},
|
||||
// })
|
||||
func (that *HoTimeDB) BatchInsert(table string, dataList []Map) int64 {
|
||||
if len(dataList) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 从第一条数据提取所有列名(确保顺序一致)
|
||||
columns := make([]string, 0)
|
||||
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
|
||||
|
||||
for k := range dataList[0] {
|
||||
realKey := k
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
realKey = strings.Replace(k, "[#]", "", -1)
|
||||
rawValues[realKey] = ObjToStr(dataList[0][k])
|
||||
}
|
||||
columns = append(columns, realKey)
|
||||
}
|
||||
|
||||
// 排序列名以确保一致性
|
||||
sort.Strings(columns)
|
||||
|
||||
// 构建列名部分
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = "`" + col + "`"
|
||||
}
|
||||
colStr := strings.Join(quotedCols, ", ")
|
||||
|
||||
// 构建每行的占位符和值
|
||||
placeholders := make([]string, len(dataList))
|
||||
values := make([]interface{}, 0, len(dataList)*len(columns))
|
||||
|
||||
for i, data := range dataList {
|
||||
rowPlaceholders := make([]string, len(columns))
|
||||
for j, col := range columns {
|
||||
// 检查是否有 [#] 标记
|
||||
rawKey := col + "[#]"
|
||||
if rawVal, ok := data[rawKey]; ok {
|
||||
// 直接 SQL 表达式
|
||||
rowPlaceholders[j] = ObjToStr(rawVal)
|
||||
} else if _, isRaw := rawValues[col]; isRaw && i == 0 {
|
||||
// 第一条数据中的 [#] 标记
|
||||
rowPlaceholders[j] = rawValues[col]
|
||||
} else if val, ok := data[col]; ok {
|
||||
// 普通值
|
||||
rowPlaceholders[j] = "?"
|
||||
values = append(values, val)
|
||||
} else {
|
||||
// 字段不存在,使用 NULL
|
||||
rowPlaceholders[j] = "NULL"
|
||||
}
|
||||
}
|
||||
placeholders[i] = "(" + strings.Join(rowPlaceholders, ", ") + ")"
|
||||
}
|
||||
|
||||
query := "INSERT INTO `" + that.Prefix + table + "` (" + colStr + ") VALUES " + strings.Join(placeholders, ", ")
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
rows64 := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows64, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果插入成功,删除缓存
|
||||
if rows64 != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows64
|
||||
}
|
||||
|
||||
// Upsert 插入或更新数据
|
||||
// table: 表名
|
||||
// data: 要插入的数据
|
||||
// uniqueKeys: 唯一键字段(用于冲突检测),支持 Slice{"id"} 或 Slice{"col1", "col2"}
|
||||
// updateColumns: 冲突时要更新的字段(如果为空,则更新所有非唯一键字段)
|
||||
// 返回受影响的行数
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// affected := db.Upsert("user",
|
||||
// Map{"id": 1, "name": "张三", "email": "zhang@example.com"},
|
||||
// Slice{"id"}, // 唯一键
|
||||
// Slice{"name", "email"}, // 冲突时更新的字段
|
||||
// )
|
||||
func (that *HoTimeDB) Upsert(table string, data Map, uniqueKeys Slice, updateColumns ...interface{}) int64 {
|
||||
if len(data) == 0 || len(uniqueKeys) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 转换 uniqueKeys 为 []string
|
||||
uniqueKeyStrs := make([]string, len(uniqueKeys))
|
||||
for i, uk := range uniqueKeys {
|
||||
uniqueKeyStrs[i] = ObjToStr(uk)
|
||||
}
|
||||
|
||||
// 转换 updateColumns 为 []string
|
||||
var updateColumnStrs []string
|
||||
if len(updateColumns) > 0 {
|
||||
// 支持两种调用方式:Upsert(table, data, Slice{"id"}, Slice{"name"}) 或 Upsert(table, data, Slice{"id"}, "name", "email")
|
||||
if slice, ok := updateColumns[0].(Slice); ok {
|
||||
updateColumnStrs = make([]string, len(slice))
|
||||
for i, col := range slice {
|
||||
updateColumnStrs[i] = ObjToStr(col)
|
||||
}
|
||||
} else {
|
||||
updateColumnStrs = make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
updateColumnStrs[i] = ObjToStr(col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集列和值
|
||||
columns := make([]string, 0, len(data))
|
||||
values := make([]interface{}, 0, len(data))
|
||||
rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值
|
||||
|
||||
for k, v := range data {
|
||||
if Substr(k, len(k)-3, 3) == "[#]" {
|
||||
realKey := strings.Replace(k, "[#]", "", -1)
|
||||
columns = append(columns, realKey)
|
||||
rawValues[realKey] = ObjToStr(v)
|
||||
} else {
|
||||
columns = append(columns, k)
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定更新字段,则更新所有非唯一键字段
|
||||
if len(updateColumnStrs) == 0 {
|
||||
uniqueKeySet := make(map[string]bool)
|
||||
for _, uk := range uniqueKeyStrs {
|
||||
uniqueKeySet[uk] = true
|
||||
}
|
||||
for _, col := range columns {
|
||||
if !uniqueKeySet[col] {
|
||||
updateColumnStrs = append(updateColumnStrs, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 SQL
|
||||
var query string
|
||||
dbType := that.Type
|
||||
if dbType == "" {
|
||||
dbType = "mysql"
|
||||
}
|
||||
|
||||
switch dbType {
|
||||
case "postgres", "postgresql":
|
||||
query = that.buildPostgresUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
case "sqlite3", "sqlite":
|
||||
query = that.buildSQLiteUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
default: // mysql
|
||||
query = that.buildMySQLUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues)
|
||||
}
|
||||
|
||||
res, err := that.Exec(query, values...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
// buildMySQLUpsert 构建 MySQL 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildMySQLUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2)
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = "`" + col + "`"
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "?"
|
||||
}
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = "`" + col + "` = " + raw
|
||||
} else {
|
||||
updateParts[i] = "`" + col + "` = VALUES(`" + col + "`)"
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO `" + that.Prefix + table + "` (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON DUPLICATE KEY UPDATE " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// buildPostgresUpsert 构建 PostgreSQL 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildPostgresUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES ($1, $2)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
paramIndex := 1
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = "\"" + col + "\""
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "$" + ObjToStr(paramIndex)
|
||||
paramIndex++
|
||||
}
|
||||
}
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = "\"" + key + "\""
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = "\"" + col + "\" = " + raw
|
||||
} else {
|
||||
updateParts[i] = "\"" + col + "\" = EXCLUDED.\"" + col + "\""
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
|
||||
") DO UPDATE SET " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// buildSQLiteUpsert 构建 SQLite 的 Upsert 语句
|
||||
func (that *HoTimeDB) buildSQLiteUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1
|
||||
|
||||
quotedCols := make([]string, len(columns))
|
||||
valueParts := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = "\"" + col + "\""
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
valueParts[i] = raw
|
||||
} else {
|
||||
valueParts[i] = "?"
|
||||
}
|
||||
}
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = "\"" + key + "\""
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if raw, ok := rawValues[col]; ok {
|
||||
updateParts[i] = "\"" + col + "\" = " + raw
|
||||
} else {
|
||||
updateParts[i] = "\"" + col + "\" = excluded.\"" + col + "\""
|
||||
}
|
||||
}
|
||||
|
||||
return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") +
|
||||
") VALUES (" + strings.Join(valueParts, ", ") +
|
||||
") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") +
|
||||
") DO UPDATE SET " + strings.Join(updateParts, ", ")
|
||||
}
|
||||
|
||||
// Update 更新数据
|
||||
func (that *HoTimeDB) Update(table string, data Map, where Map) int64 {
|
||||
query := "UPDATE `" + that.Prefix + table + "` SET "
|
||||
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 := that.where(where)
|
||||
|
||||
query += temp + ";"
|
||||
qs = append(qs, resWhere...)
|
||||
|
||||
res, err := that.Exec(query, qs...)
|
||||
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果更新成功,则删除缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
// Delete 删除数据
|
||||
func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 {
|
||||
query := "DELETE FROM `" + that.Prefix + table + "` "
|
||||
|
||||
temp, resWhere := that.where(data)
|
||||
query += temp + ";"
|
||||
|
||||
res, err := that.Exec(query, resWhere...)
|
||||
rows := int64(0)
|
||||
if err.GetError() == nil && res != nil {
|
||||
rows, _ = res.RowsAffected()
|
||||
}
|
||||
|
||||
// 如果删除成功,删除对应缓存
|
||||
if rows != 0 {
|
||||
if that.HoTimeCache != nil && table != "cached" {
|
||||
_ = that.HoTimeCache.Db(table+"*", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"code.hoteas.com/golang/hotime/cache"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HoTimeDB 数据库操作核心结构体
|
||||
type HoTimeDB struct {
|
||||
*sql.DB
|
||||
ContextBase
|
||||
DBName string
|
||||
*cache.HoTimeCache
|
||||
Log *logrus.Logger
|
||||
Type string // 数据库类型: mysql, sqlite3, postgres
|
||||
Prefix string
|
||||
LastQuery string
|
||||
LastData []interface{}
|
||||
ConnectFunc func(err ...*Error) (*sql.DB, *sql.DB)
|
||||
LastErr *Error
|
||||
limit Slice
|
||||
*sql.Tx //事务对象
|
||||
SlaveDB *sql.DB // 从数据库
|
||||
Mode int // mode为0生产模式,1为测试模式,2为开发模式
|
||||
mu sync.RWMutex
|
||||
limitMu sync.Mutex
|
||||
Dialect Dialect // 数据库方言适配器
|
||||
}
|
||||
|
||||
// SetConnect 设置数据库配置连接
|
||||
func (that *HoTimeDB) SetConnect(connect func(err ...*Error) (master, slave *sql.DB), err ...*Error) {
|
||||
that.ConnectFunc = connect
|
||||
_ = that.InitDb(err...)
|
||||
}
|
||||
|
||||
// InitDb 初始化数据库连接
|
||||
func (that *HoTimeDB) InitDb(err ...*Error) *Error {
|
||||
if len(err) != 0 {
|
||||
that.LastErr = err[0]
|
||||
}
|
||||
that.DB, that.SlaveDB = that.ConnectFunc(that.LastErr)
|
||||
if that.DB == nil {
|
||||
return that.LastErr
|
||||
}
|
||||
e := that.DB.Ping()
|
||||
|
||||
that.LastErr.SetError(e)
|
||||
|
||||
if that.SlaveDB != nil {
|
||||
e := that.SlaveDB.Ping()
|
||||
that.LastErr.SetError(e)
|
||||
}
|
||||
|
||||
// 根据数据库类型初始化方言适配器
|
||||
if that.Dialect == nil {
|
||||
that.initDialect()
|
||||
}
|
||||
|
||||
return that.LastErr
|
||||
}
|
||||
|
||||
// initDialect 根据数据库类型初始化方言
|
||||
func (that *HoTimeDB) initDialect() {
|
||||
switch that.Type {
|
||||
case "postgres", "postgresql":
|
||||
that.Dialect = &PostgreSQLDialect{}
|
||||
case "sqlite3", "sqlite":
|
||||
that.Dialect = &SQLiteDialect{}
|
||||
default:
|
||||
that.Dialect = &MySQLDialect{}
|
||||
}
|
||||
}
|
||||
|
||||
// GetDialect 获取当前方言适配器
|
||||
func (that *HoTimeDB) GetDialect() Dialect {
|
||||
if that.Dialect == nil {
|
||||
that.initDialect()
|
||||
}
|
||||
return that.Dialect
|
||||
}
|
||||
|
||||
// SetDialect 设置方言适配器
|
||||
func (that *HoTimeDB) SetDialect(dialect Dialect) {
|
||||
that.Dialect = dialect
|
||||
}
|
||||
|
||||
// GetType 获取数据库类型
|
||||
func (that *HoTimeDB) GetType() string {
|
||||
return that.Type
|
||||
}
|
||||
|
||||
// GetPrefix 获取表前缀
|
||||
func (that *HoTimeDB) GetPrefix() string {
|
||||
return that.Prefix
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dialect 数据库方言接口
|
||||
// 用于处理不同数据库之间的语法差异
|
||||
type Dialect interface {
|
||||
// Quote 对表名/字段名添加引号
|
||||
// MySQL 使用反引号 `name`
|
||||
// PostgreSQL 使用双引号 "name"
|
||||
// SQLite 使用双引号或方括号 "name" 或 [name]
|
||||
Quote(name string) string
|
||||
|
||||
// Placeholder 生成占位符
|
||||
// MySQL/SQLite 使用 ?
|
||||
// PostgreSQL 使用 $1, $2, $3...
|
||||
Placeholder(index int) string
|
||||
|
||||
// Placeholders 生成多个占位符,用逗号分隔
|
||||
Placeholders(count int, startIndex int) string
|
||||
|
||||
// SupportsLastInsertId 是否支持 LastInsertId
|
||||
// PostgreSQL 不支持,需要使用 RETURNING
|
||||
SupportsLastInsertId() bool
|
||||
|
||||
// ReturningClause 生成 RETURNING 子句(用于 PostgreSQL)
|
||||
ReturningClause(column string) string
|
||||
|
||||
// UpsertSQL 生成 Upsert 语句
|
||||
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE ...
|
||||
// PostgreSQL: INSERT ... ON CONFLICT ... DO UPDATE SET ...
|
||||
// SQLite: INSERT OR REPLACE / INSERT ... ON CONFLICT ...
|
||||
UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string
|
||||
|
||||
// GetName 获取方言名称
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// MySQLDialect MySQL 方言实现
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func (d *MySQLDialect) GetName() string {
|
||||
return "mysql"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "`" + name + "`"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Placeholder(index int) string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) SupportsLastInsertId() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) ReturningClause(column string) string {
|
||||
return "" // MySQL 不支持 RETURNING
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2)
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
// 检查是否是 [#] 标记的直接 SQL
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
// 这种情况在调用处处理
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = VALUES(" + quotedCol + ")"
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
|
||||
// PostgreSQLDialect PostgreSQL 方言实现
|
||||
type PostgreSQLDialect struct{}
|
||||
|
||||
func (d *PostgreSQLDialect) GetName() string {
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Placeholder(index int) string {
|
||||
return fmt.Sprintf("$%d", index)
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = fmt.Sprintf("$%d", startIndex+i)
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) SupportsLastInsertId() bool {
|
||||
return false // PostgreSQL 需要使用 RETURNING
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) ReturningClause(column string) string {
|
||||
return " RETURNING " + d.Quote(column)
|
||||
}
|
||||
|
||||
func (d *PostgreSQLDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES ($1, $2)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1, col2 = EXCLUDED.col2
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = d.Quote(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = EXCLUDED." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(quotedUniqueKeys, ", "),
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
|
||||
// SQLiteDialect SQLite 方言实现
|
||||
type SQLiteDialect struct{}
|
||||
|
||||
func (d *SQLiteDialect) GetName() string {
|
||||
return "sqlite3"
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Quote(name string) string {
|
||||
// 如果已经包含点号(表.字段)或空格(别名),不添加引号
|
||||
if strings.Contains(name, ".") || strings.Contains(name, " ") {
|
||||
return name
|
||||
}
|
||||
return "\"" + name + "\""
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Placeholder(index int) string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) Placeholders(count int, startIndex int) string {
|
||||
if count <= 0 {
|
||||
return ""
|
||||
}
|
||||
placeholders := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
return strings.Join(placeholders, ",")
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) SupportsLastInsertId() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) ReturningClause(column string) string {
|
||||
return "" // SQLite 3.35+ 支持 RETURNING,但为兼容性暂不使用
|
||||
}
|
||||
|
||||
func (d *SQLiteDialect) UpsertSQL(table string, columns []string, uniqueKeys []string, updateColumns []string) string {
|
||||
// INSERT INTO table (col1, col2) VALUES (?, ?)
|
||||
// ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1, col2 = excluded.col2
|
||||
quotedCols := make([]string, len(columns))
|
||||
for i, col := range columns {
|
||||
quotedCols[i] = d.Quote(col)
|
||||
}
|
||||
|
||||
placeholders := d.Placeholders(len(columns), 1)
|
||||
|
||||
quotedUniqueKeys := make([]string, len(uniqueKeys))
|
||||
for i, key := range uniqueKeys {
|
||||
quotedUniqueKeys[i] = d.Quote(key)
|
||||
}
|
||||
|
||||
updateParts := make([]string, len(updateColumns))
|
||||
for i, col := range updateColumns {
|
||||
if strings.HasSuffix(col, "[#]") {
|
||||
updateParts[i] = col
|
||||
} else {
|
||||
quotedCol := d.Quote(col)
|
||||
updateParts[i] = quotedCol + " = excluded." + quotedCol
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) ON CONFLICT (%s) DO UPDATE SET %s",
|
||||
d.Quote(table),
|
||||
strings.Join(quotedCols, ", "),
|
||||
placeholders,
|
||||
strings.Join(quotedUniqueKeys, ", "),
|
||||
strings.Join(updateParts, ", "))
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// md5 生成查询的 MD5 哈希(用于缓存)
|
||||
func (that *HoTimeDB) md5(query string, args ...interface{}) string {
|
||||
strByte, _ := json.Marshal(args)
|
||||
str := Md5(query + ":" + string(strByte))
|
||||
return str
|
||||
}
|
||||
|
||||
// Query 执行查询 SQL
|
||||
func (that *HoTimeDB) Query(query string, args ...interface{}) []Map {
|
||||
return that.queryWithRetry(query, false, args...)
|
||||
}
|
||||
|
||||
// queryWithRetry 内部查询方法,支持重试标记
|
||||
func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interface{}) []Map {
|
||||
// 保存调试信息(加锁保护)
|
||||
that.mu.Lock()
|
||||
that.LastQuery = query
|
||||
that.LastData = args
|
||||
that.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if that.Mode != 0 {
|
||||
that.mu.RLock()
|
||||
that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError())
|
||||
that.mu.RUnlock()
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
var resl *sql.Rows
|
||||
|
||||
// 主从数据库切换,只有select语句有从数据库
|
||||
db := that.DB
|
||||
if that.SlaveDB != nil {
|
||||
db = that.SlaveDB
|
||||
}
|
||||
|
||||
if db == nil {
|
||||
err = errors.New("没有初始化数据库")
|
||||
that.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理参数中的 slice 类型
|
||||
processedArgs := that.processArgs(args)
|
||||
|
||||
if that.Tx != nil {
|
||||
resl, err = that.Tx.Query(query, processedArgs...)
|
||||
} else {
|
||||
resl, err = db.Query(query, processedArgs...)
|
||||
}
|
||||
|
||||
that.LastErr.SetError(err)
|
||||
if err != nil {
|
||||
// 如果还没重试过,尝试 Ping 后重试一次
|
||||
if !retried {
|
||||
if pingErr := db.Ping(); pingErr == nil {
|
||||
return that.queryWithRetry(query, true, args...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return that.Row(resl)
|
||||
}
|
||||
|
||||
// Exec 执行非查询 SQL
|
||||
func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) {
|
||||
return that.execWithRetry(query, false, args...)
|
||||
}
|
||||
|
||||
// execWithRetry 内部执行方法,支持重试标记
|
||||
func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interface{}) (sql.Result, *Error) {
|
||||
// 保存调试信息(加锁保护)
|
||||
that.mu.Lock()
|
||||
that.LastQuery = query
|
||||
that.LastData = args
|
||||
that.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
if that.Mode != 0 {
|
||||
that.mu.RLock()
|
||||
that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError())
|
||||
that.mu.RUnlock()
|
||||
}
|
||||
}()
|
||||
|
||||
var e error
|
||||
var resl sql.Result
|
||||
|
||||
if that.DB == nil {
|
||||
err := errors.New("没有初始化数据库")
|
||||
that.LastErr.SetError(err)
|
||||
return nil, that.LastErr
|
||||
}
|
||||
|
||||
// 处理参数中的 slice 类型
|
||||
processedArgs := that.processArgs(args)
|
||||
|
||||
if that.Tx != nil {
|
||||
resl, e = that.Tx.Exec(query, processedArgs...)
|
||||
} else {
|
||||
resl, e = that.DB.Exec(query, processedArgs...)
|
||||
}
|
||||
|
||||
that.LastErr.SetError(e)
|
||||
// 判断是否连接断开了,如果还没重试过,尝试重试一次
|
||||
if e != nil {
|
||||
if !retried {
|
||||
if pingErr := that.DB.Ping(); pingErr == nil {
|
||||
return that.execWithRetry(query, true, args...)
|
||||
}
|
||||
}
|
||||
return resl, that.LastErr
|
||||
}
|
||||
|
||||
return resl, that.LastErr
|
||||
}
|
||||
|
||||
// processArgs 处理参数中的 slice 类型
|
||||
func (that *HoTimeDB) processArgs(args []interface{}) []interface{} {
|
||||
processedArgs := make([]interface{}, len(args))
|
||||
copy(processedArgs, args)
|
||||
for key := range processedArgs {
|
||||
arg := processedArgs[key]
|
||||
if arg == nil {
|
||||
continue
|
||||
}
|
||||
argType := reflect.ValueOf(arg).Type().String()
|
||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
||||
argLis := ObjToSlice(arg)
|
||||
// 将slice转为逗号分割字符串
|
||||
argStr := ""
|
||||
for i := 0; i < len(argLis); i++ {
|
||||
if i == len(argLis)-1 {
|
||||
argStr += ObjToStr(argLis[i])
|
||||
} else {
|
||||
argStr += ObjToStr(argLis[i]) + ","
|
||||
}
|
||||
}
|
||||
processedArgs[key] = argStr
|
||||
}
|
||||
}
|
||||
return processedArgs
|
||||
}
|
||||
|
||||
// Row 数据库数据解析
|
||||
func (that *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]
|
||||
}
|
||||
err := resl.Scan(b...)
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return nil
|
||||
}
|
||||
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] // 取实际类型
|
||||
}
|
||||
}
|
||||
|
||||
dest = append(dest, lis)
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
-- HoTimeDB 测试表结构
|
||||
-- 用于测试和示例的MySQL表结构定义
|
||||
-- 请根据实际需要修改表结构和字段类型
|
||||
|
||||
-- 创建数据库(可选)
|
||||
CREATE DATABASE IF NOT EXISTS `hotimedb_test` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE `hotimedb_test`;
|
||||
|
||||
-- 用户表
|
||||
DROP TABLE IF EXISTS `app_user`;
|
||||
CREATE TABLE `app_user` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||||
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '用户姓名',
|
||||
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱地址',
|
||||
`password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码hash',
|
||||
`phone` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
|
||||
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
|
||||
`gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0-未知 1-男 2-女',
|
||||
`avatar` varchar(500) NOT NULL DEFAULT '' COMMENT '头像URL',
|
||||
`level` varchar(20) NOT NULL DEFAULT 'normal' COMMENT '用户等级 normal-普通 vip-会员 svip-超级会员',
|
||||
`balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '账户余额',
|
||||
`login_count` int(11) NOT NULL DEFAULT '0' COMMENT '登录次数',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1-正常 0-禁用 -1-删除',
|
||||
`last_login` datetime DEFAULT NULL COMMENT '最后登录时间',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_email` (`email`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_level` (`level`),
|
||||
KEY `idx_created_time` (`created_time`),
|
||||
KEY `idx_deleted_at` (`deleted_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||
|
||||
-- 用户资料表
|
||||
DROP TABLE IF EXISTS `app_profile`;
|
||||
CREATE TABLE `app_profile` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
|
||||
`real_name` varchar(50) NOT NULL DEFAULT '' COMMENT '真实姓名',
|
||||
`id_card` varchar(20) NOT NULL DEFAULT '' COMMENT '身份证号',
|
||||
`address` varchar(500) NOT NULL DEFAULT '' COMMENT '地址',
|
||||
`bio` text COMMENT '个人简介',
|
||||
`verified` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否认证 1-是 0-否',
|
||||
`preferences` json DEFAULT NULL COMMENT '用户偏好设置JSON',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_id` (`user_id`),
|
||||
KEY `idx_verified` (`verified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资料表';
|
||||
|
||||
-- 部门表
|
||||
DROP TABLE IF EXISTS `app_department`;
|
||||
CREATE TABLE `app_department` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '部门ID',
|
||||
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '部门名称',
|
||||
`parent_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '上级部门ID',
|
||||
`manager_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '部门经理ID',
|
||||
`description` text COMMENT '部门描述',
|
||||
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1-正常 0-禁用',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_parent_id` (`parent_id`),
|
||||
KEY `idx_manager_id` (`manager_id`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表';
|
||||
|
||||
-- 商品表
|
||||
DROP TABLE IF EXISTS `app_product`;
|
||||
CREATE TABLE `app_product` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品ID',
|
||||
`title` varchar(200) NOT NULL DEFAULT '' COMMENT '商品标题',
|
||||
`description` text COMMENT '商品描述',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '商品价格',
|
||||
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
|
||||
`category_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '分类ID',
|
||||
`brand` varchar(100) NOT NULL DEFAULT '' COMMENT '品牌',
|
||||
`tags` varchar(500) NOT NULL DEFAULT '' COMMENT '标签,逗号分隔',
|
||||
`images` json DEFAULT NULL COMMENT '商品图片JSON数组',
|
||||
`attributes` json DEFAULT NULL COMMENT '商品属性JSON',
|
||||
`sales_count` int(11) NOT NULL DEFAULT '0' COMMENT '销售数量',
|
||||
`view_count` int(11) NOT NULL DEFAULT '0' COMMENT '浏览数量',
|
||||
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1-上架 0-下架',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_category_id` (`category_id`),
|
||||
KEY `idx_price` (`price`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_created_time` (`created_time`),
|
||||
FULLTEXT KEY `ft_title_description` (`title`,`description`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';
|
||||
|
||||
-- 订单表
|
||||
DROP TABLE IF EXISTS `app_order`;
|
||||
CREATE TABLE `app_order` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单ID',
|
||||
`order_no` varchar(50) NOT NULL DEFAULT '' COMMENT '订单号',
|
||||
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
|
||||
`product_id` bigint(20) unsigned NOT NULL COMMENT '商品ID',
|
||||
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '单价',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '总金额',
|
||||
`discount_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
|
||||
`final_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '实付金额',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '订单状态 pending-待付款 paid-已付款 shipped-已发货 completed-已完成 cancelled-已取消',
|
||||
`payment_method` varchar(20) NOT NULL DEFAULT '' COMMENT '支付方式',
|
||||
`shipping_address` json DEFAULT NULL COMMENT '收货地址JSON',
|
||||
`remark` text COMMENT '订单备注',
|
||||
`paid_time` datetime DEFAULT NULL COMMENT '支付时间',
|
||||
`shipped_time` datetime DEFAULT NULL COMMENT '发货时间',
|
||||
`completed_time` datetime DEFAULT NULL COMMENT '完成时间',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_order_no` (`order_no`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_product_id` (`product_id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_created_time` (`created_time`),
|
||||
KEY `idx_paid_time` (`paid_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';
|
||||
|
||||
-- 订单详情表
|
||||
DROP TABLE IF EXISTS `app_order_detail`;
|
||||
CREATE TABLE `app_order_detail` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`order_id` bigint(20) unsigned NOT NULL COMMENT '订单ID',
|
||||
`product_id` bigint(20) unsigned NOT NULL COMMENT '商品ID',
|
||||
`product_title` varchar(200) NOT NULL DEFAULT '' COMMENT '商品标题',
|
||||
`product_image` varchar(500) NOT NULL DEFAULT '' COMMENT '商品图片',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '单价',
|
||||
`quantity` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '小计',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_product_id` (`product_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单详情表';
|
||||
|
||||
-- 支付日志表
|
||||
DROP TABLE IF EXISTS `app_payment_log`;
|
||||
CREATE TABLE `app_payment_log` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
|
||||
`order_id` bigint(20) unsigned DEFAULT NULL COMMENT '订单ID',
|
||||
`transaction_id` varchar(100) NOT NULL DEFAULT '' COMMENT '交易ID',
|
||||
`type` varchar(20) NOT NULL DEFAULT '' COMMENT '类型 order_payment-订单支付 recharge-充值 refund-退款',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '金额',
|
||||
`method` varchar(20) NOT NULL DEFAULT '' COMMENT '支付方式 balance-余额 alipay-支付宝 wechat-微信',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '状态 pending-处理中 success-成功 failed-失败',
|
||||
`description` varchar(255) NOT NULL DEFAULT '' COMMENT '描述',
|
||||
`extra_data` json DEFAULT NULL COMMENT '额外数据JSON',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_order_id` (`order_id`),
|
||||
KEY `idx_transaction_id` (`transaction_id`),
|
||||
KEY `idx_type` (`type`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_created_time` (`created_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付日志表';
|
||||
|
||||
-- 转账日志表
|
||||
DROP TABLE IF EXISTS `app_transfer_log`;
|
||||
CREATE TABLE `app_transfer_log` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`from_user_id` bigint(20) unsigned NOT NULL COMMENT '转出用户ID',
|
||||
`to_user_id` bigint(20) unsigned NOT NULL COMMENT '转入用户ID',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '转账金额',
|
||||
`type` varchar(20) NOT NULL DEFAULT 'transfer' COMMENT '类型',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'success' COMMENT '状态',
|
||||
`description` varchar(255) NOT NULL DEFAULT '' COMMENT '描述',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_from_user_id` (`from_user_id`),
|
||||
KEY `idx_to_user_id` (`to_user_id`),
|
||||
KEY `idx_created_time` (`created_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='转账日志表';
|
||||
|
||||
-- 操作日志表
|
||||
DROP TABLE IF EXISTS `app_operation_log`;
|
||||
CREATE TABLE `app_operation_log` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
|
||||
`module` varchar(50) NOT NULL DEFAULT '' COMMENT '模块',
|
||||
`action` varchar(50) NOT NULL DEFAULT '' COMMENT '操作',
|
||||
`description` varchar(255) NOT NULL DEFAULT '' COMMENT '描述',
|
||||
`ip` varchar(45) NOT NULL DEFAULT '' COMMENT 'IP地址',
|
||||
`user_agent` varchar(500) NOT NULL DEFAULT '' COMMENT '用户代理',
|
||||
`request_data` json DEFAULT NULL COMMENT '请求数据JSON',
|
||||
`response_data` json DEFAULT NULL COMMENT '响应数据JSON',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态 1-成功 0-失败',
|
||||
`execution_time` int(11) NOT NULL DEFAULT '0' COMMENT '执行时间(毫秒)',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`),
|
||||
KEY `idx_module` (`module`),
|
||||
KEY `idx_action` (`action`),
|
||||
KEY `idx_created_time` (`created_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='操作日志表';
|
||||
|
||||
-- 缓存表(HoTimeDB内置缓存使用)
|
||||
DROP TABLE IF EXISTS `app_cached`;
|
||||
CREATE TABLE `app_cached` (
|
||||
`key` varchar(255) NOT NULL COMMENT '缓存键',
|
||||
`value` longtext COMMENT '缓存值',
|
||||
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`key`),
|
||||
KEY `idx_expire_time` (`expire_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='缓存表';
|
||||
|
||||
-- 批量用户表(用于批量操作示例)
|
||||
DROP TABLE IF EXISTS `app_user_batch`;
|
||||
CREATE TABLE `app_user_batch` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '用户姓名',
|
||||
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱地址',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_created_time` (`created_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='批量用户表';
|
||||
|
||||
-- 插入测试数据
|
||||
INSERT INTO `app_user` (`name`, `email`, `password`, `age`, `level`, `balance`, `status`, `created_time`) VALUES
|
||||
('张三', 'zhangsan@example.com', 'hashed_password_1', 25, 'normal', 1000.00, 1, '2023-01-15 10:30:00'),
|
||||
('李四', 'lisi@example.com', 'hashed_password_2', 30, 'vip', 5000.00, 1, '2023-02-20 14:20:00'),
|
||||
('王五', 'wangwu@example.com', 'hashed_password_3', 28, 'svip', 10000.00, 1, '2023-03-10 16:45:00'),
|
||||
('赵六', 'zhaoliu@example.com', 'hashed_password_4', 35, 'normal', 500.00, 1, '2023-04-05 09:15:00'),
|
||||
('钱七', 'qianqi@example.com', 'hashed_password_5', 22, 'vip', 2500.00, 0, '2023-05-12 11:30:00');
|
||||
|
||||
INSERT INTO `app_profile` (`user_id`, `real_name`, `verified`) VALUES
|
||||
(1, '张三', 1),
|
||||
(2, '李四', 1),
|
||||
(3, '王五', 1),
|
||||
(4, '赵六', 0),
|
||||
(5, '钱七', 0);
|
||||
|
||||
INSERT INTO `app_department` (`name`, `parent_id`, `description`) VALUES
|
||||
('技术部', 0, '负责技术开发和维护'),
|
||||
('产品部', 0, '负责产品设计和规划'),
|
||||
('市场部', 0, '负责市场推广和销售'),
|
||||
('前端组', 1, '负责前端开发'),
|
||||
('后端组', 1, '负责后端开发');
|
||||
|
||||
INSERT INTO `app_product` (`title`, `description`, `price`, `stock`, `category_id`, `brand`, `sales_count`) VALUES
|
||||
('苹果手机', '最新款苹果手机,性能强劲', 6999.00, 100, 1, '苹果', 50),
|
||||
('华为手机', '国产精品手机,拍照出色', 4999.00, 200, 1, '华为', 80),
|
||||
('小米手机', '性价比之王,配置丰富', 2999.00, 300, 1, '小米', 120),
|
||||
('联想笔记本', '商务办公首选,稳定可靠', 5999.00, 50, 2, '联想', 30),
|
||||
('戴尔笔记本', '游戏性能出色,散热良好', 8999.00, 30, 2, '戴尔', 15);
|
||||
|
||||
INSERT INTO `app_order` (`order_no`, `user_id`, `product_id`, `quantity`, `price`, `amount`, `final_amount`, `status`, `created_time`) VALUES
|
||||
('ORD202301150001', 1, 1, 1, 6999.00, 6999.00, 6999.00, 'paid', '2023-01-15 15:30:00'),
|
||||
('ORD202301160001', 2, 2, 2, 4999.00, 9998.00, 9998.00, 'paid', '2023-01-16 10:20:00'),
|
||||
('ORD202301170001', 3, 3, 1, 2999.00, 2999.00, 2999.00, 'completed', '2023-01-17 14:15:00'),
|
||||
('ORD202301180001', 1, 4, 1, 5999.00, 5999.00, 5999.00, 'pending', '2023-01-18 16:45:00'),
|
||||
('ORD202301190001', 4, 5, 1, 8999.00, 8999.00, 8999.00, 'cancelled', '2023-01-19 11:30:00');
|
||||
|
||||
INSERT INTO `app_order_detail` (`order_id`, `product_id`, `product_title`, `price`, `quantity`, `amount`) VALUES
|
||||
(1, 1, '苹果手机', 6999.00, 1, 6999.00),
|
||||
(2, 2, '华为手机', 4999.00, 2, 9998.00),
|
||||
(3, 3, '小米手机', 2999.00, 1, 2999.00),
|
||||
(4, 4, '联想笔记本', 5999.00, 1, 5999.00),
|
||||
(5, 5, '戴尔笔记本', 8999.00, 1, 8999.00);
|
||||
|
||||
INSERT INTO `app_payment_log` (`user_id`, `order_id`, `type`, `amount`, `method`, `status`, `description`) VALUES
|
||||
(1, 1, 'order_payment', 6999.00, 'balance', 'success', '订单支付'),
|
||||
(2, 2, 'order_payment', 9998.00, 'alipay', 'success', '订单支付'),
|
||||
(3, 3, 'order_payment', 2999.00, 'wechat', 'success', '订单支付'),
|
||||
(2, NULL, 'recharge', 10000.00, 'alipay', 'success', '账户充值'),
|
||||
(3, NULL, 'recharge', 5000.00, 'wechat', 'success', '账户充值');
|
||||
|
||||
-- 创建索引优化查询性能
|
||||
CREATE INDEX idx_user_email_status ON app_user(email, status);
|
||||
CREATE INDEX idx_order_user_status ON app_order(user_id, status);
|
||||
CREATE INDEX idx_order_created_status ON app_order(created_time, status);
|
||||
CREATE INDEX idx_product_category_status ON app_product(category_id, status);
|
||||
|
||||
-- 创建视图(可选)
|
||||
CREATE OR REPLACE VIEW v_user_order_stats AS
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
u.name as user_name,
|
||||
u.email,
|
||||
u.level,
|
||||
COUNT(o.id) as order_count,
|
||||
COALESCE(SUM(o.final_amount), 0) as total_amount,
|
||||
COALESCE(AVG(o.final_amount), 0) as avg_amount,
|
||||
MAX(o.created_time) as last_order_time
|
||||
FROM app_user u
|
||||
LEFT JOIN app_order o ON u.id = o.user_id AND o.status IN ('paid', 'completed')
|
||||
WHERE u.status = 1
|
||||
GROUP BY u.id;
|
||||
|
||||
-- 存储过程示例(可选)
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE GetUserOrderSummary(IN p_user_id BIGINT)
|
||||
BEGIN
|
||||
SELECT
|
||||
u.name,
|
||||
u.email,
|
||||
u.level,
|
||||
u.balance,
|
||||
COUNT(o.id) as order_count,
|
||||
COALESCE(SUM(CASE WHEN o.status = 'paid' THEN o.final_amount END), 0) as paid_amount,
|
||||
COALESCE(SUM(CASE WHEN o.status = 'completed' THEN o.final_amount END), 0) as completed_amount
|
||||
FROM app_user u
|
||||
LEFT JOIN app_order o ON u.id = o.user_id
|
||||
WHERE u.id = p_user_id AND u.status = 1
|
||||
GROUP BY u.id;
|
||||
END //
|
||||
DELIMITER ;
|
||||
|
||||
-- 显示表结构信息
|
||||
SELECT
|
||||
TABLE_NAME as '表名',
|
||||
TABLE_COMMENT as '表注释',
|
||||
TABLE_ROWS as '预估行数',
|
||||
ROUND(DATA_LENGTH/1024/1024, 2) as '数据大小(MB)'
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'app_%'
|
||||
ORDER BY TABLE_NAME;
|
||||
@@ -0,0 +1,57 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Action 事务操作
|
||||
// 如果 action 返回 true 则提交事务;返回 false 则回滚
|
||||
func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) {
|
||||
db := HoTimeDB{
|
||||
DB: that.DB,
|
||||
ContextBase: that.ContextBase,
|
||||
DBName: that.DBName,
|
||||
HoTimeCache: that.HoTimeCache,
|
||||
Log: that.Log,
|
||||
Type: that.Type,
|
||||
Prefix: that.Prefix,
|
||||
LastQuery: that.LastQuery,
|
||||
LastData: that.LastData,
|
||||
ConnectFunc: that.ConnectFunc,
|
||||
LastErr: that.LastErr,
|
||||
limit: that.limit,
|
||||
Tx: that.Tx,
|
||||
SlaveDB: that.SlaveDB,
|
||||
Mode: that.Mode,
|
||||
Dialect: that.Dialect,
|
||||
mu: sync.RWMutex{},
|
||||
limitMu: sync.Mutex{},
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
db.Tx = tx
|
||||
|
||||
isSuccess = action(db)
|
||||
|
||||
if !isSuccess {
|
||||
err = db.Tx.Rollback()
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return isSuccess
|
||||
}
|
||||
return isSuccess
|
||||
}
|
||||
|
||||
err = db.Tx.Commit()
|
||||
if err != nil {
|
||||
that.LastErr.SetError(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
+548
@@ -0,0 +1,548 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 条件关键字
|
||||
var condition = []string{"AND", "OR"}
|
||||
|
||||
// 特殊关键字(支持大小写)
|
||||
var vcond = []string{"GROUP", "ORDER", "LIMIT", "DISTINCT", "HAVING", "OFFSET"}
|
||||
|
||||
// normalizeKey 标准化关键字(转大写)
|
||||
func normalizeKey(k string) string {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range vcond {
|
||||
if upper == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
for _, v := range condition {
|
||||
if upper == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// isConditionKey 判断是否是条件关键字
|
||||
func isConditionKey(k string) bool {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range condition {
|
||||
if upper == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isVcondKey 判断是否是特殊关键字
|
||||
func isVcondKey(k string) bool {
|
||||
upper := strings.ToUpper(k)
|
||||
for _, v := range vcond {
|
||||
if upper == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// where 语句解析
|
||||
func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
|
||||
// 标准化 Map 的 key(大小写兼容)
|
||||
normalizedData := Map{}
|
||||
for k, v := range data {
|
||||
normalizedData[normalizeKey(k)] = v
|
||||
}
|
||||
data = normalizedData
|
||||
|
||||
// 收集所有 key 并排序
|
||||
testQu := []string{}
|
||||
for key := range data {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
|
||||
// 追踪普通条件数量,用于自动添加 AND
|
||||
normalCondCount := 0
|
||||
|
||||
for _, k := range testQu {
|
||||
v := data[k]
|
||||
|
||||
// 检查是否是 AND/OR 条件关键字
|
||||
if isConditionKey(k) {
|
||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
||||
where += tw
|
||||
res = append(res, ts...)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否是特殊关键字(GROUP, ORDER, LIMIT 等)
|
||||
if isVcondKey(k) {
|
||||
continue // 特殊关键字在后面单独处理
|
||||
}
|
||||
|
||||
// 处理普通条件字段
|
||||
if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 {
|
||||
continue
|
||||
}
|
||||
if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
tv, vv := that.varCond(k, v)
|
||||
if tv != "" {
|
||||
// 自动添加 AND 连接符
|
||||
if normalCondCount > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
where += tv
|
||||
normalCondCount++
|
||||
res = append(res, vv...)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 WHERE 关键字
|
||||
if len(where) != 0 {
|
||||
hasWhere := true
|
||||
for _, v := range vcond {
|
||||
if strings.Index(where, v) == 0 {
|
||||
hasWhere = false
|
||||
}
|
||||
}
|
||||
|
||||
if hasWhere {
|
||||
where = " WHERE " + where + " "
|
||||
}
|
||||
}
|
||||
|
||||
// 处理特殊字符(按固定顺序:GROUP, HAVING, ORDER, LIMIT, OFFSET)
|
||||
specialOrder := []string{"GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "DISTINCT"}
|
||||
for _, vcondKey := range specialOrder {
|
||||
v, exists := data[vcondKey]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
switch vcondKey {
|
||||
case "GROUP":
|
||||
where += " GROUP BY "
|
||||
where += that.formatVcondValue(v)
|
||||
case "HAVING":
|
||||
// HAVING 条件处理
|
||||
if havingMap, ok := v.(Map); ok {
|
||||
havingWhere, havingRes := that.cond("AND", havingMap)
|
||||
if havingWhere != "" {
|
||||
where += " HAVING " + strings.TrimSpace(havingWhere) + " "
|
||||
res = append(res, havingRes...)
|
||||
}
|
||||
}
|
||||
case "ORDER":
|
||||
where += " ORDER BY "
|
||||
where += that.formatVcondValue(v)
|
||||
case "LIMIT":
|
||||
where += " LIMIT "
|
||||
where += that.formatVcondValue(v)
|
||||
case "OFFSET":
|
||||
where += " OFFSET " + ObjToStr(v) + " "
|
||||
case "DISTINCT":
|
||||
// DISTINCT 通常在 SELECT 中处理,这里暂时忽略
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// formatVcondValue 格式化特殊关键字的值
|
||||
func (that *HoTimeDB) formatVcondValue(v interface{}) string {
|
||||
result := ""
|
||||
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
result += " " + vs.GetString(i) + " "
|
||||
if len(vs) != i+1 {
|
||||
result += ", "
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += " " + ObjToStr(v) + " "
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// varCond 变量条件解析
|
||||
func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) {
|
||||
where := ""
|
||||
res := make([]interface{}, 0)
|
||||
length := len(k)
|
||||
|
||||
if k == "[#]" {
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else if k == "[##]" {
|
||||
// 直接添加 SQL 片段(key 为 [##] 时)
|
||||
where += " " + ObjToStr(v) + " "
|
||||
} else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' {
|
||||
def := false
|
||||
|
||||
switch Substr(k, length-3, 3) {
|
||||
case "[>]":
|
||||
k = strings.Replace(k, "[>]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + ">? "
|
||||
res = append(res, v)
|
||||
case "[<]":
|
||||
k = strings.Replace(k, "[<]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + "<? "
|
||||
res = append(res, v)
|
||||
case "[!]":
|
||||
k = strings.Replace(k, "[!]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where, res = that.notIn(k, v, where, res)
|
||||
case "[#]":
|
||||
k = strings.Replace(k, "[#]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += " " + k + "=" + ObjToStr(v) + " "
|
||||
case "[##]": // 直接添加value到sql,需要考虑防注入
|
||||
where += " " + ObjToStr(v)
|
||||
case "[#!]":
|
||||
k = strings.Replace(k, "[#!]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
||||
case "[!#]":
|
||||
k = strings.Replace(k, "[!#]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
||||
case "[~]":
|
||||
k = strings.Replace(k, "[~]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " LIKE ? "
|
||||
v = "%" + ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[!~]": // 左边任意
|
||||
k = strings.Replace(k, "[!~]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " LIKE ? "
|
||||
v = "%" + ObjToStr(v) + ""
|
||||
res = append(res, v)
|
||||
case "[~!]": // 右边任意
|
||||
k = strings.Replace(k, "[~!]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " LIKE ? "
|
||||
v = ObjToStr(v) + "%"
|
||||
res = append(res, v)
|
||||
case "[~~]": // 手动任意
|
||||
k = strings.Replace(k, "[~~]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " LIKE ? "
|
||||
res = append(res, v)
|
||||
default:
|
||||
def = true
|
||||
}
|
||||
|
||||
if def {
|
||||
switch Substr(k, length-4, 4) {
|
||||
case "[>=]":
|
||||
k = strings.Replace(k, "[>=]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + ">=? "
|
||||
res = append(res, v)
|
||||
case "[<=]":
|
||||
k = strings.Replace(k, "[<=]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + "<=? "
|
||||
res = append(res, v)
|
||||
case "[><]":
|
||||
k = strings.Replace(k, "[><]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " NOT BETWEEN ? AND ? "
|
||||
vs := ObjToSlice(v)
|
||||
res = append(res, vs[0])
|
||||
res = append(res, vs[1])
|
||||
case "[<>]":
|
||||
k = strings.Replace(k, "[<>]", "", -1)
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
where += k + " BETWEEN ? AND ? "
|
||||
vs := ObjToSlice(v)
|
||||
res = append(res, vs[0])
|
||||
res = append(res, vs[1])
|
||||
default:
|
||||
where, res = that.handleDefaultCondition(k, v, where, res)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
where, res = that.handlePlainField(k, v, where, res)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// handleDefaultCondition 处理默认条件(带方括号但不是特殊操作符)
|
||||
func (that *HoTimeDB) handleDefaultCondition(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
|
||||
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
return where, res
|
||||
}
|
||||
|
||||
if len(vs) == 1 {
|
||||
where += k + "=? "
|
||||
res = append(res, vs[0])
|
||||
return where, res
|
||||
}
|
||||
|
||||
// IN 优化:连续整数转为 BETWEEN
|
||||
where, res = that.optimizeInCondition(k, vs, where, res)
|
||||
} else {
|
||||
where += k + "=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// handlePlainField 处理普通字段(无方括号)
|
||||
func (that *HoTimeDB) handlePlainField(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
if !strings.Contains(k, ".") {
|
||||
k = "`" + k + "` "
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
where += k + " IS NULL "
|
||||
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
return where, res
|
||||
}
|
||||
|
||||
if len(vs) == 1 {
|
||||
where += k + "=? "
|
||||
res = append(res, vs[0])
|
||||
return where, res
|
||||
}
|
||||
|
||||
// IN 优化
|
||||
where, res = that.optimizeInCondition(k, vs, where, res)
|
||||
} else {
|
||||
where += k + "=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// optimizeInCondition 优化 IN 条件(连续整数转为 BETWEEN)
|
||||
func (that *HoTimeDB) optimizeInCondition(k string, vs Slice, where string, res []interface{}) (string, []interface{}) {
|
||||
min := int64(0)
|
||||
isMin := true
|
||||
IsRange := true
|
||||
num := int64(0)
|
||||
isNum := true
|
||||
|
||||
where1 := ""
|
||||
res1 := Slice{}
|
||||
where2 := k + " IN ("
|
||||
res2 := Slice{}
|
||||
|
||||
for kvs := 0; kvs <= len(vs); kvs++ {
|
||||
vsv := int64(0)
|
||||
if kvs < len(vs) {
|
||||
vsv = vs.GetCeilInt64(kvs)
|
||||
// 确保是全部是int类型
|
||||
if ObjToStr(vsv) != vs.GetString(kvs) {
|
||||
IsRange = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isNum {
|
||||
isNum = false
|
||||
num = vsv
|
||||
} else {
|
||||
num++
|
||||
}
|
||||
if isMin {
|
||||
isMin = false
|
||||
min = vsv
|
||||
}
|
||||
// 不等于则到了分路口
|
||||
if num != vsv {
|
||||
// between
|
||||
if num-min > 1 {
|
||||
if where1 != "" {
|
||||
where1 += " OR " + k + " BETWEEN ? AND ? "
|
||||
} else {
|
||||
where1 += k + " BETWEEN ? AND ? "
|
||||
}
|
||||
res1 = append(res1, min)
|
||||
res1 = append(res1, num-1)
|
||||
} else {
|
||||
where2 += "?,"
|
||||
res2 = append(res2, min)
|
||||
}
|
||||
min = vsv
|
||||
num = vsv
|
||||
}
|
||||
}
|
||||
|
||||
if IsRange {
|
||||
where3 := ""
|
||||
|
||||
if where1 != "" {
|
||||
where3 += where1
|
||||
res = append(res, res1...)
|
||||
}
|
||||
|
||||
if len(res2) == 1 {
|
||||
if where3 == "" {
|
||||
where3 += k + " = ? "
|
||||
} else {
|
||||
where3 += " OR " + k + " = ? "
|
||||
}
|
||||
res = append(res, res2...)
|
||||
} else if len(res2) > 1 {
|
||||
where2 = where2[:len(where2)-1]
|
||||
if where3 == "" {
|
||||
where3 += where2 + ")"
|
||||
} else {
|
||||
where3 += " OR " + where2 + ")"
|
||||
}
|
||||
res = append(res, res2...)
|
||||
}
|
||||
|
||||
if where3 != "" {
|
||||
where += "(" + where3 + ")"
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// 非连续整数,使用普通 IN
|
||||
where += k + " IN ("
|
||||
res = append(res, vs...)
|
||||
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if i+1 != len(vs) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// notIn NOT IN 条件处理
|
||||
func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) {
|
||||
if v == nil {
|
||||
where += k + " IS NOT NULL "
|
||||
} else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
||||
vs := ObjToSlice(v)
|
||||
if len(vs) == 0 {
|
||||
return where, res
|
||||
}
|
||||
where += k + " NOT IN ("
|
||||
res = append(res, vs...)
|
||||
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if i+1 != len(vs) {
|
||||
where += "?,"
|
||||
} else {
|
||||
where += "?) "
|
||||
}
|
||||
}
|
||||
} else {
|
||||
where += k + " !=? "
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
|
||||
// cond 条件组合处理
|
||||
func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) {
|
||||
where := " "
|
||||
res := make([]interface{}, 0)
|
||||
lens := len(data)
|
||||
|
||||
testQu := []string{}
|
||||
for key := range data {
|
||||
testQu = append(testQu, key)
|
||||
}
|
||||
sort.Strings(testQu)
|
||||
|
||||
for _, k := range testQu {
|
||||
v := data[k]
|
||||
x := 0
|
||||
for i := 0; i < len(condition); i++ {
|
||||
if condition[i] == strings.ToUpper(k) {
|
||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
||||
if lens--; lens <= 0 {
|
||||
where += "(" + tw + ") "
|
||||
} else {
|
||||
where += "(" + tw + ") " + tag + " "
|
||||
}
|
||||
|
||||
res = append(res, ts...)
|
||||
break
|
||||
}
|
||||
x++
|
||||
}
|
||||
|
||||
if x == len(condition) {
|
||||
tv, vv := that.varCond(k, v)
|
||||
if tv == "" {
|
||||
lens--
|
||||
continue
|
||||
}
|
||||
res = append(res, vv...)
|
||||
if lens--; lens <= 0 {
|
||||
where += tv + ""
|
||||
} else {
|
||||
where += tv + " " + tag + " "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return where, res
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
//"fmt"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
ApiCode string
|
||||
Url string
|
||||
}
|
||||
|
||||
var Company = company{}
|
||||
|
||||
func (that *company) Init(apiCode string) {
|
||||
//"06c6a07e89dd45c88de040ee1489eef7"
|
||||
that.ApiCode = apiCode
|
||||
that.Url = "http://api.81api.com"
|
||||
}
|
||||
|
||||
// GetCompanyOtherAll 获取企业基础信息
|
||||
func (that *company) GetCompanyOtherAll(name string) Map {
|
||||
|
||||
res := Map{}
|
||||
data, e := that.GetCompanyPatentsInfo(name) //获取专利信息
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["PatentsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyOtherCopyrightsInfo(name) //获取其他专利
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["OtherCopyrightsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyTrademarksInfo(name) //获取商标
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["TrademarksInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanySoftwareCopyrightsInfo(name) //获取软著
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["SoftwareCopyrightsInfo"] = data.GetMap("data")
|
||||
}
|
||||
data, e = that.GetCompanyProfileTags(name) //获取大数据标签
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
res["ProfileTags"] = data.GetSlice("data")
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetCompanyBaseInfo 获取企业基础信息
|
||||
func (that *company) GetCompanyList(name string) (Map, error) {
|
||||
url := "/fuzzyQueryCompanyInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyBaseInfo 获取企业基础信息
|
||||
func (that *company) GetCompanyBaseInfo(name string) (Map, error) {
|
||||
url := "/getCompanyBaseInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyPatentsInfo 获取专利信息
|
||||
func (that *company) GetCompanyPatentsInfo(name string) (Map, error) {
|
||||
url := "/getCompanyPatentsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyTrademarksInfo 获取商标信息
|
||||
func (that *company) GetCompanyTrademarksInfo(name string) (Map, error) {
|
||||
url := "/getCompanyTrademarksInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanySoftwareCopyrightsInfo 获取软著信息
|
||||
func (that *company) GetCompanySoftwareCopyrightsInfo(name string) (Map, error) {
|
||||
url := "/getCompanySoftwareCopyrightsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyOtherCopyrightsInfo 获取其他著作信息
|
||||
func (that *company) GetCompanyOtherCopyrightsInfo(name string) (Map, error) {
|
||||
url := "/getCompanyOtherCopyrightsInfo/"
|
||||
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
|
||||
// GetCompanyProfileTags 获取大数据标签
|
||||
func (that *company) GetCompanyProfileTags(name string) (Map, error) {
|
||||
url := "/getCompanyProfileTags/"
|
||||
body, err := that.basePost(url, name)
|
||||
return ObjToMap(body), err
|
||||
}
|
||||
func (that *company) basePost(url string, name string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
reqest, err := http.NewRequest("GET", that.Url+url+name+"/?isRaiseErrorCode=1", nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
reqest.Header.Add("Authorization", "APPCODE "+that.ApiCode)
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res := string(body)
|
||||
fmt.Println(res)
|
||||
return res, err
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"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
|
||||
}
|
||||
|
||||
// from 源坐标类型:
|
||||
// 1:GPS标准坐标;
|
||||
// 2:搜狗地图坐标;
|
||||
// 3:火星坐标(gcj02),即高德地图、腾讯地图和MapABC等地图使用的坐标;
|
||||
// 4:3中列举的地图坐标对应的墨卡托平面坐标;
|
||||
// 5:百度地图采用的经纬度坐标(bd09ll);
|
||||
// 6:百度地图采用的墨卡托平面坐标(bd09mc);
|
||||
// 7:图吧地图坐标;
|
||||
// 8:51地图坐标;
|
||||
// int 1 1 否
|
||||
// to
|
||||
// 目标坐标类型:
|
||||
// 3:火星坐标(gcj02),即高德地图、腾讯地图及MapABC等地图使用的坐标;
|
||||
// 5:百度地图采用的经纬度坐标(bd09ll);
|
||||
// 6:百度地图采用的墨卡托平面坐标(bd09mc);
|
||||
func (that *baiduMap) Geoconv(latlngs []Map, from, to int) (Slice, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
latlngsStr := ""
|
||||
for _, v := range latlngs {
|
||||
if latlngsStr != "" {
|
||||
latlngsStr = latlngsStr + ";" + v.GetString("lng") + "," + v.GetString("lat")
|
||||
} else {
|
||||
latlngsStr = v.GetString("lng") + "," + v.GetString("lat")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
url := "https://api.map.baidu.com/geoconv/v1/?from=" + ObjToStr(from) + "&to=" + ObjToStr(to) + "&ak=" + that.Ak + "&coords=" + latlngsStr
|
||||
reqest, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Println(string(body))
|
||||
data := ObjToMap(string(body))
|
||||
if data.GetCeilInt64("status") != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data.GetSlice("result"), err
|
||||
}
|
||||
|
||||
func (that *baiduMap) GetAddress(lat string, lng string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
url := "https://api.map.baidu.com/reverse_geocoding/v3/?ak=" + that.Ak + "&output=json&location=" + lat + "," + lng
|
||||
reqest, err := http.NewRequest("GET", url, 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
|
||||
|
||||
}
|
||||
|
||||
// GetPosition 获取定位列表
|
||||
func (that *baiduMap) GetPosition(name string, region string) (string, error) {
|
||||
|
||||
client := &http.Client{}
|
||||
if region == "" {
|
||||
region = "全国"
|
||||
}
|
||||
reqest, err := http.NewRequest("GET", that.Url+"&query="+url.PathEscape(name)+"®ion="+url.PathEscape(region), nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
response, err := client.Do(reqest)
|
||||
defer response.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error ", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
//fmt.Println(string(body))
|
||||
|
||||
return string(body), err
|
||||
|
||||
}
|
||||
+20
-18
@@ -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("连接错误")
|
||||
}
|
||||
@@ -72,8 +74,8 @@ func (this *DDY) send(mUrl string, umoblie string, content string) (bool, error)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
//调用url发送短信的连接
|
||||
func (this *DDY) httpsPostForm(url string, data url.Values) (string, error) {
|
||||
// 调用url发送短信的连接
|
||||
func (that *dingdongyun) httpsPostForm(url string, data url.Values) (string, error) {
|
||||
resp, err := http.PostForm(url, data)
|
||||
|
||||
if err != nil {
|
||||
|
||||
+11
-10
@@ -2,37 +2,38 @@ package download
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go.hoteas.com/hotime"
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
//下载文件
|
||||
func Down(url, path, name string) bool {
|
||||
// Down 下载文件
|
||||
func Down(url, path, name string, e ...*Error) bool {
|
||||
|
||||
os.MkdirAll(path, os.ModeDir)
|
||||
if hotime.Substr(path, len(path)-1, len(path)) != "/" {
|
||||
if Substr(path, len(path)-1, len(path)) != "/" {
|
||||
path = path + "/"
|
||||
}
|
||||
out, err := os.Create(path + name)
|
||||
e := hotime.Error{}
|
||||
if err != nil {
|
||||
e.SetError(err)
|
||||
|
||||
if err != nil && 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package rsa
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func FileGet(path string) []byte {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
//读取文件的内容
|
||||
info, _ := file.Stat()
|
||||
buf := make([]byte, info.Size())
|
||||
file.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
// RSA_Encrypt RSA加密
|
||||
// plainText 要加密的数据
|
||||
// path 公钥匙文件地址
|
||||
func RSA_Encrypt(plainText []byte, buf []byte) []byte {
|
||||
//pem解码
|
||||
block, _ := pem.Decode(buf)
|
||||
//x509解码
|
||||
|
||||
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//类型断言
|
||||
publicKey := publicKeyInterface.(*rsa.PublicKey)
|
||||
//对明文进行加密
|
||||
cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//返回密文
|
||||
return cipherText
|
||||
}
|
||||
|
||||
// RSA_Decrypt RSA解密
|
||||
// cipherText 需要解密的byte数据
|
||||
// path 私钥文件路径
|
||||
func RSA_Decrypt(cipherText []byte, buf []byte) []byte {
|
||||
|
||||
//pem解码
|
||||
block, _ := pem.Decode(buf)
|
||||
//X509解码
|
||||
private, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//对密文进行解密
|
||||
//plainText,_:=rsa.DecryptPKCS1v15(rand.Reader,privateKey,cipherText)
|
||||
|
||||
v, err := rsa.DecryptPKCS1v15(rand.Reader, private.(*rsa.PrivateKey), cipherText)
|
||||
//返回明文
|
||||
return v
|
||||
}
|
||||
func MarshalPKCS8PrivateKey(key *rsa.PrivateKey) []byte {
|
||||
info := struct {
|
||||
Version int
|
||||
PrivateKeyAlgorithm []asn1.ObjectIdentifier
|
||||
PrivateKey []byte
|
||||
}{}
|
||||
info.Version = 0
|
||||
info.PrivateKeyAlgorithm = make([]asn1.ObjectIdentifier, 1)
|
||||
info.PrivateKeyAlgorithm[0] = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
|
||||
info.PrivateKey = x509.MarshalPKCS1PrivateKey(key)
|
||||
|
||||
k, err := asn1.Marshal(info)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func Demo() {
|
||||
//生成密钥对,保存到文件
|
||||
GenerateRSAKey(2048, "./")
|
||||
//加密
|
||||
data := []byte("hello world")
|
||||
encrypt := RSA_Encrypt(data, FileGet("public.pem"))
|
||||
fmt.Println(string(encrypt))
|
||||
|
||||
// 解密
|
||||
decrypt := RSA_Decrypt(encrypt, FileGet("private.pem"))
|
||||
fmt.Println(string(decrypt))
|
||||
}
|
||||
|
||||
// GenerateRSAKey 生成RSA私钥和公钥,保存到文件中
|
||||
// bits 证书大小
|
||||
func GenerateRSAKey(bits int, path string) {
|
||||
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
|
||||
//Reader是一个全局、共享的密码用强随机数生成器
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//保存私钥
|
||||
//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
|
||||
X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
//使用pem格式对x509输出的内容进行编码
|
||||
//创建文件保存私钥
|
||||
privateFile, err := os.Create(path + "private.pem")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer privateFile.Close()
|
||||
//构建一个pem.Block结构体对象
|
||||
privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
|
||||
//将数据保存到文件
|
||||
pem.Encode(privateFile, &privateBlock)
|
||||
|
||||
//保存公钥
|
||||
//获取公钥的数据
|
||||
publicKey := privateKey.PublicKey
|
||||
//X509对公钥编码
|
||||
X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//pem格式编码
|
||||
//创建用于保存公钥的文件
|
||||
publicFile, err := os.Create(path + "public.pem")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer publicFile.Close()
|
||||
//创建一个pem.Block结构体对象
|
||||
publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
|
||||
//保存到文件
|
||||
pem.Encode(publicFile, &publicBlock)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package tencent
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type company struct {
|
||||
secretId string
|
||||
secretKey string
|
||||
}
|
||||
|
||||
var Company = company{}
|
||||
|
||||
func (that *company) Init(secretId, secretKey string) {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
that.secretId = secretId
|
||||
that.secretKey = secretKey
|
||||
}
|
||||
|
||||
func (that *company) calcAuthorization(source string) (auth string, datetime string, err error) {
|
||||
|
||||
timeLocation, _ := time.LoadLocation("Etc/GMT")
|
||||
datetime = time.Now().In(timeLocation).Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
signStr := fmt.Sprintf("x-date: %s\nx-source: %s", datetime, source)
|
||||
|
||||
// hmac-sha1
|
||||
mac := hmac.New(sha1.New, []byte(that.secretKey))
|
||||
mac.Write([]byte(signStr))
|
||||
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
auth = fmt.Sprintf("hmac id=\"%s\", algorithm=\"hmac-sha1\", headers=\"x-date x-source\", signature=\"%s\"",
|
||||
that.secretId, sign)
|
||||
|
||||
return auth, datetime, nil
|
||||
}
|
||||
|
||||
func (that *company) urlencode(params map[string]string) string {
|
||||
var p = gourl.Values{}
|
||||
for k, v := range params {
|
||||
p.Add(k, v)
|
||||
}
|
||||
return p.Encode()
|
||||
}
|
||||
|
||||
func (that *company) GetCompany(name string) Map {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
source := "market"
|
||||
|
||||
// 签名
|
||||
auth, datetime, _ := that.calcAuthorization(source)
|
||||
|
||||
// 请求方法
|
||||
method := "GET"
|
||||
// 请求头
|
||||
headers := map[string]string{"X-Source": source, "X-Date": datetime, "Authorization": auth}
|
||||
|
||||
// 查询参数
|
||||
queryParams := make(map[string]string)
|
||||
queryParams["keyword"] = name
|
||||
// body参数
|
||||
bodyParams := make(map[string]string)
|
||||
|
||||
// url参数拼接
|
||||
url := "https://service-3jnh3ku8-1256140209.gz.apigw.tencentcs.com/release/business4/geet"
|
||||
if len(queryParams) > 0 {
|
||||
url = fmt.Sprintf("%s?%s", url, that.urlencode(queryParams))
|
||||
}
|
||||
|
||||
bodyMethods := map[string]bool{"POST": true, "PUT": true, "PATCH": true}
|
||||
var body io.Reader = nil
|
||||
if bodyMethods[method] {
|
||||
body = strings.NewReader(that.urlencode(bodyParams))
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
request, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
for k, v := range headers {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil
|
||||
}
|
||||
res := string(bodyBytes)
|
||||
fmt.Println(res)
|
||||
return ObjToMap(res)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package tencent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
|
||||
)
|
||||
|
||||
type tencent struct {
|
||||
secretId string
|
||||
secretKey string
|
||||
credential *common.Credential
|
||||
}
|
||||
|
||||
var Tencent = tencent{}
|
||||
|
||||
func (that *tencent) Init(secretId, secretKey string) {
|
||||
// 云市场分配的密钥Id
|
||||
//secretId := "xxxx"
|
||||
//// 云市场分配的密钥Key
|
||||
//secretKey := "xxxx"
|
||||
that.secretId = secretId
|
||||
that.secretKey = secretKey
|
||||
that.credential = common.NewCredential(
|
||||
that.secretId,
|
||||
that.secretKey,
|
||||
)
|
||||
}
|
||||
|
||||
func (that *tencent) OCRCOMPANY(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewBizLicenseOCRRequest()
|
||||
|
||||
//request.ImageUrl = common.StringPtr("https://img0.baidu.com/it/u=2041013181,3227632688&fm=26&fmt=auto")
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.BizLicenseOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
|
||||
func (that *tencent) OCR(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewGeneralAccurateOCRRequest()
|
||||
|
||||
//request.ImageUrl = common.StringPtr("https://img0.baidu.com/it/u=2041013181,3227632688&fm=26&fmt=auto")
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.GeneralAccurateOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
|
||||
func (that *tencent) Qrcode(base64Str string) string {
|
||||
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
||||
|
||||
request := ocr.NewQrcodeOCRRequest()
|
||||
|
||||
request.ImageBase64 = common.StringPtr(base64Str)
|
||||
|
||||
response, err := client.QrcodeOCR(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("An API error has returned: %s", err)
|
||||
return ""
|
||||
}
|
||||
//fmt.Printf("%s", response.ToJsonString())
|
||||
|
||||
return response.ToJsonString()
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
. "code.hoteas.com/golang/hotime/common"
|
||||
"errors"
|
||||
"go.hoteas.com/hotime"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -15,7 +15,7 @@ type Upload struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
||||
func (that *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
||||
Request.ParseMultipartForm(32 << 20)
|
||||
var filePath string
|
||||
files := Request.MultipartForm.File
|
||||
@@ -36,7 +36,7 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
||||
data := time.Unix(int64(t), 0).Format("2006-01")
|
||||
path := ""
|
||||
if strings.EqualFold(savefilepath, "") {
|
||||
path = this.Path + data
|
||||
path = that.Path + data
|
||||
} else {
|
||||
path = savefilepath + data
|
||||
}
|
||||
@@ -50,8 +50,8 @@ func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
||||
}
|
||||
}
|
||||
}
|
||||
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + hotime.ObjToStr(hotime.Rand(6))
|
||||
filePath = path + "/" + filename + this.Path
|
||||
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
|
||||
filePath = path + "/" + filename + that.Path
|
||||
} else {
|
||||
filePath = savePath
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/officialaccount"
|
||||
h5config "github.com/silenceper/wechat/v2/officialaccount/config"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/js"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/oauth"
|
||||
)
|
||||
|
||||
// 基于此文档开发
|
||||
// https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type h5Program struct {
|
||||
Memory *cache.Memory
|
||||
Config *h5config.Config
|
||||
*officialaccount.OfficialAccount
|
||||
weixin *wechat.Wechat //微信登录实例
|
||||
}
|
||||
|
||||
var H5Program = h5Program{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *h5Program) Init(appid string, appsecret string) {
|
||||
that.weixin = wechat.NewWechat()
|
||||
that.Memory = cache.NewMemory()
|
||||
that.Config = &h5config.Config{
|
||||
AppID: appid,
|
||||
AppSecret: appsecret,
|
||||
//Token: "xxx",
|
||||
//EncodingAESKey: "xxxx",
|
||||
Cache: that.Memory,
|
||||
}
|
||||
that.OfficialAccount = that.weixin.GetOfficialAccount(that.Config)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (that *h5Program) GetUserInfo(code string) (appid string, resToken oauth.ResAccessToken, userInfo oauth.UserInfo, err error) {
|
||||
auth := that.GetOauth()
|
||||
//weixin.GetOpenPlatform()
|
||||
resToken, err = auth.GetUserAccessToken(code)
|
||||
if err != nil {
|
||||
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
//getUserInfo
|
||||
userInfo, err = auth.GetUserInfo(resToken.AccessToken, resToken.OpenID, "")
|
||||
if err != nil {
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
return auth.AppID, resToken, userInfo, err
|
||||
}
|
||||
|
||||
// GetSignUrl js url签名
|
||||
func (that *h5Program) GetSignUrl(signUrl string) (*js.Config, error) {
|
||||
|
||||
js := that.OfficialAccount.GetJs()
|
||||
cfg1, e := js.GetConfig(signUrl)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return cfg1, nil
|
||||
}
|
||||
|
||||
// GetSignUrl js url签名
|
||||
//func (that *h5Program) GetJsPay(signUrl string) (*js.Config, error) {
|
||||
// //
|
||||
// //js := that.OfficialAccount().GetJs()
|
||||
// //
|
||||
// //cfg1, e := js.GetConfig(signUrl)
|
||||
// //if e != nil {
|
||||
// // return nil, e
|
||||
// //}
|
||||
//
|
||||
// return cfg1, nil
|
||||
//}
|
||||
@@ -0,0 +1,66 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/silenceper/wechat/v2"
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/miniprogram"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/auth"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/config"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
||||
)
|
||||
|
||||
type miniProgram struct {
|
||||
Memory *cache.Memory
|
||||
Config *config.Config
|
||||
weixin *wechat.Wechat //微信登录实例
|
||||
*miniprogram.MiniProgram
|
||||
}
|
||||
|
||||
var MiniProgram = miniProgram{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *miniProgram) Init(appid string, appsecret string) {
|
||||
|
||||
that.weixin = wechat.NewWechat()
|
||||
that.Memory = cache.NewMemory()
|
||||
that.Config = &config.Config{
|
||||
AppID: appid,
|
||||
AppSecret: appsecret,
|
||||
//Token: "xxx",
|
||||
//EncodingAESKey: "xxxx",
|
||||
Cache: that.Memory,
|
||||
}
|
||||
that.MiniProgram = that.weixin.GetMiniProgram(that.Config)
|
||||
}
|
||||
|
||||
func (that *miniProgram) GetBaseUserInfo(code string) (appid string, re auth.ResCode2Session, err error) {
|
||||
appid = that.Config.AppID
|
||||
a := that.GetAuth()
|
||||
re, err = a.Code2Session(code)
|
||||
|
||||
if err != nil {
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
func (that *miniProgram) GetPhoneNumber(sessionkey, encryptedData, iv string) (appid string, re *encryptor.PlainData, err error) {
|
||||
appid = that.Config.AppID
|
||||
|
||||
if sessionkey == "" || encryptedData == "" || iv == "" {
|
||||
return appid, re, errors.New("参数不足")
|
||||
}
|
||||
|
||||
eny := that.GetEncryptor()
|
||||
|
||||
re, err = eny.Decrypt(sessionkey, encryptedData, iv)
|
||||
|
||||
if err != nil {
|
||||
return appid, re, err
|
||||
}
|
||||
|
||||
return appid, re, err
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package wechat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/wechat/v3"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 基于此文档开发
|
||||
// https://github.com/silenceper/wechat/blob/v2/doc/api/officialaccount.md
|
||||
type wxpay struct {
|
||||
client *wechat.ClientV3
|
||||
ctx context.Context
|
||||
apiV3Key string
|
||||
MchId string
|
||||
}
|
||||
|
||||
var WxPay = wxpay{}
|
||||
|
||||
// Init 初始化
|
||||
func (that *wxpay) Init(MchId, SerialNo, APIv3Key, PrivateKey string) {
|
||||
client, err := wechat.NewClientV3(MchId, SerialNo, APIv3Key, PrivateKey)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
that.client = client
|
||||
that.apiV3Key = APIv3Key
|
||||
that.MchId = MchId
|
||||
// 设置微信平台API证书和序列号(如开启自动验签,请忽略此步骤)
|
||||
//client.SetPlatformCert([]byte(""), "")
|
||||
that.ctx = context.Background()
|
||||
// 启用自动同步返回验签,并定时更新微信平台API证书(开启自动验签时,无需单独设置微信平台API证书和序列号)
|
||||
err = client.AutoVerifySign()
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 打开Debug开关,输出日志,默认是关闭的
|
||||
client.DebugSwitch = gopay.DebugOn
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (that *wxpay) GetJsOrder(money int64, appid, openid, name, tradeNo, notifyUrl string) (jsApiParams *wechat.JSAPIPayParams, err error) {
|
||||
fmt.Println("dasdas", money, appid, name, tradeNo, notifyUrl)
|
||||
PrepayId, err := that.getPrepayId(money, appid, that.MchId, openid, name, tradeNo, notifyUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//小程序
|
||||
jsapi, err := that.client.PaySignOfJSAPI(appid, PrepayId)
|
||||
return jsapi, err
|
||||
}
|
||||
|
||||
func (that *wxpay) CallbackJsOrder(req *http.Request) (*wechat.V3DecryptResult, error) {
|
||||
|
||||
notifyReq, err := wechat.V3ParseNotify(req)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wxPublicKey 通过 client.WxPublicKey() 获取
|
||||
err = notifyReq.VerifySignByPK(that.client.WxPublicKey())
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ========异步通知敏感信息解密========
|
||||
// 普通支付通知解密
|
||||
result, err := notifyReq.DecryptCipherText(that.apiV3Key)
|
||||
|
||||
//that.client.V3TransactionQueryOrder(that.ctx,result.BankType,result.OR)
|
||||
|
||||
return result, err
|
||||
|
||||
// 合单支付通知解密
|
||||
//result, err := notifyReq.DecryptCombineCipherText(apiV3Key)
|
||||
//// 退款通知解密
|
||||
//result, err := notifyReq.DecryptRefundCipherText(apiV3Key)
|
||||
|
||||
// ========异步通知应答========
|
||||
// 退款通知http应答码为200且返回状态码为SUCCESS才会当做商户接收成功,否则会重试。
|
||||
// 注意:重试过多会导致微信支付端积压过多通知而堵塞,影响其他正常通知。
|
||||
|
||||
// 此写法是 gin 框架返回微信的写法
|
||||
//c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
|
||||
//
|
||||
//// 此写法是 echo 框架返回微信的写法
|
||||
//return c.JSON(http.StatusOK, &wechat.V3NotifyRsp{Code: gopay.SUCCESS, Message: "成功"})
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
//func (that *wxpay) GetMiniOrder(money int64,appid,name,tradeNo,notifyUrl string) (jsApiParams *wechat.AppletParams,err error){
|
||||
//
|
||||
// PrepayId,err:=that.getPrepayId(money,name,tradeNo,notifyUrl)
|
||||
// if err!=nil{
|
||||
// return nil,err
|
||||
// }
|
||||
//
|
||||
// //小程序
|
||||
// applet, err := that.client.PaySignOfApplet(appid,PrepayId)
|
||||
//
|
||||
// return applet,err
|
||||
//}
|
||||
|
||||
func (that *wxpay) getPrepayId(money int64, appid, mchid, openid, name, tradeNo, notifyUrl string) (prepayid string, err error) {
|
||||
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
|
||||
// 初始化 BodyMap
|
||||
bm := make(gopay.BodyMap)
|
||||
bm.Set("appid", appid).
|
||||
Set("mchid", mchid).
|
||||
//Set("sub_mchid", "sub_mchid").
|
||||
Set("description", name).
|
||||
Set("out_trade_no", tradeNo).
|
||||
Set("time_expire", expire).
|
||||
Set("notify_url", notifyUrl).
|
||||
SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", money).
|
||||
Set("currency", "CNY")
|
||||
}).
|
||||
SetBodyMap("payer", func(bm gopay.BodyMap) {
|
||||
bm.Set("openid", openid)
|
||||
})
|
||||
//ctx:=context.Context()
|
||||
|
||||
wxRsp, err := that.client.V3TransactionJsapi(that.ctx, bm)
|
||||
fmt.Println("获取PrepayId", wxRsp, err)
|
||||
if err != nil {
|
||||
//xlog.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return wxRsp.Response.PrepayId, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package hotime
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
//框架层处理错误
|
||||
type Error struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (this *Error) GetError() error {
|
||||
|
||||
return this.err
|
||||
|
||||
}
|
||||
|
||||
func (this *Error) SetError(err error, loglevel ...int) {
|
||||
|
||||
this.err = nil
|
||||
if err == nil {
|
||||
this.err = err
|
||||
return
|
||||
}
|
||||
|
||||
lev := Config.GetInt("logLevel")
|
||||
|
||||
if len(loglevel) != 0 {
|
||||
lev = loglevel[0]
|
||||
}
|
||||
|
||||
if lev == LOG_FMT {
|
||||
log.Println(err)
|
||||
}
|
||||
if lev == LOG_FILE {
|
||||
log.Println(err)
|
||||
}
|
||||
this.err = err
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 压测配置
|
||||
type BenchConfig struct {
|
||||
URL string
|
||||
Concurrency int
|
||||
Duration time.Duration
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// 压测结果
|
||||
type BenchResult struct {
|
||||
TotalRequests int64
|
||||
SuccessRequests int64
|
||||
FailedRequests int64
|
||||
TotalDuration time.Duration
|
||||
MinLatency int64 // 纳秒
|
||||
MaxLatency int64
|
||||
AvgLatency int64
|
||||
P50Latency int64 // 50分位
|
||||
P90Latency int64 // 90分位
|
||||
P99Latency int64 // 99分位
|
||||
QPS float64
|
||||
}
|
||||
|
||||
// 延迟收集器
|
||||
type LatencyCollector struct {
|
||||
latencies []int64
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (lc *LatencyCollector) Add(latency int64) {
|
||||
lc.mu.Lock()
|
||||
lc.latencies = append(lc.latencies, latency)
|
||||
lc.mu.Unlock()
|
||||
}
|
||||
|
||||
func (lc *LatencyCollector) GetPercentile(p float64) int64 {
|
||||
lc.mu.Lock()
|
||||
defer lc.mu.Unlock()
|
||||
|
||||
if len(lc.latencies) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 简单排序取百分位
|
||||
n := len(lc.latencies)
|
||||
idx := int(float64(n) * p)
|
||||
if idx >= n {
|
||||
idx = n - 1
|
||||
}
|
||||
|
||||
// 部分排序找第idx个元素
|
||||
return quickSelect(lc.latencies, idx)
|
||||
}
|
||||
|
||||
func quickSelect(arr []int64, k int) int64 {
|
||||
if len(arr) == 1 {
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
pivot := arr[len(arr)/2]
|
||||
var left, right, equal []int64
|
||||
|
||||
for _, v := range arr {
|
||||
if v < pivot {
|
||||
left = append(left, v)
|
||||
} else if v > pivot {
|
||||
right = append(right, v)
|
||||
} else {
|
||||
equal = append(equal, v)
|
||||
}
|
||||
}
|
||||
|
||||
if k < len(left) {
|
||||
return quickSelect(left, k)
|
||||
} else if k < len(left)+len(equal) {
|
||||
return pivot
|
||||
}
|
||||
return quickSelect(right, k-len(left)-len(equal))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 最大化利用CPU
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
fmt.Println("==========================================")
|
||||
fmt.Println(" 🔥 HoTime 极限压力测试 🔥")
|
||||
fmt.Println("==========================================")
|
||||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||||
fmt.Println()
|
||||
|
||||
// 极限测试配置
|
||||
configs := []BenchConfig{
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 500, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 1000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 2000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 5000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
{URL: "http://127.0.0.1:8081/app/test/hello", Concurrency: 10000, Duration: 15 * time.Second, Timeout: 10 * time.Second},
|
||||
}
|
||||
|
||||
// 检查服务
|
||||
fmt.Println("正在检查服务是否可用...")
|
||||
if !checkService(configs[0].URL) {
|
||||
fmt.Println("❌ 服务不可用,请先启动示例应用")
|
||||
return
|
||||
}
|
||||
fmt.Println("✅ 服务已就绪")
|
||||
fmt.Println()
|
||||
|
||||
// 预热
|
||||
fmt.Println("🔄 预热中 (5秒)...")
|
||||
warmup(configs[0].URL, 100, 5*time.Second)
|
||||
fmt.Println("✅ 预热完成")
|
||||
fmt.Println()
|
||||
|
||||
var maxQPS float64
|
||||
var maxConcurrency int
|
||||
|
||||
// 执行极限测试
|
||||
for i, config := range configs {
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
fmt.Printf("【极限测试 %d】并发数: %d, 持续时间: %v\n", i+1, config.Concurrency, config.Duration)
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
|
||||
result := runBenchmark(config)
|
||||
printResult(result)
|
||||
|
||||
if result.QPS > maxQPS {
|
||||
maxQPS = result.QPS
|
||||
maxConcurrency = config.Concurrency
|
||||
}
|
||||
|
||||
// 检查是否达到瓶颈
|
||||
successRate := float64(result.SuccessRequests) / float64(result.TotalRequests) * 100
|
||||
if successRate < 95 {
|
||||
fmt.Println("\n⚠️ 成功率低于95%,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
if result.AvgLatency > int64(100*time.Millisecond) {
|
||||
fmt.Println("\n⚠️ 平均延迟超过100ms,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// 测试间隔
|
||||
if i < len(configs)-1 {
|
||||
fmt.Println("冷却 5 秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// 最终报告
|
||||
fmt.Println()
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Println(" 📊 极限测试总结")
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Printf("最高 QPS: %.2f 请求/秒\n", maxQPS)
|
||||
fmt.Printf("最佳并发数: %d\n", maxConcurrency)
|
||||
fmt.Println()
|
||||
|
||||
// 并发用户估算
|
||||
estimateUsers(maxQPS)
|
||||
}
|
||||
|
||||
func checkService(url string) bool {
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
|
||||
func warmup(url string, concurrency int, duration time.Duration) {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func runBenchmark(config BenchConfig) BenchResult {
|
||||
var (
|
||||
totalRequests int64
|
||||
successRequests int64
|
||||
failedRequests int64
|
||||
totalLatency int64
|
||||
minLatency int64 = int64(time.Hour)
|
||||
maxLatency int64
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
collector := &LatencyCollector{
|
||||
latencies: make([]int64, 0, 100000),
|
||||
}
|
||||
|
||||
// 高性能HTTP客户端
|
||||
client := &http.Client{
|
||||
Timeout: config.Timeout,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: config.Concurrency * 2,
|
||||
MaxIdleConnsPerHost: config.Concurrency * 2,
|
||||
MaxConnsPerHost: config.Concurrency * 2,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// 启动并发
|
||||
for i := 0; i < config.Concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
reqStart := time.Now()
|
||||
success := makeRequest(client, config.URL)
|
||||
latency := time.Since(reqStart).Nanoseconds()
|
||||
|
||||
atomic.AddInt64(&totalRequests, 1)
|
||||
atomic.AddInt64(&totalLatency, latency)
|
||||
|
||||
if success {
|
||||
atomic.AddInt64(&successRequests, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failedRequests, 1)
|
||||
}
|
||||
|
||||
// 采样收集延迟(每100个请求采样1个,减少内存开销)
|
||||
if atomic.LoadInt64(&totalRequests)%100 == 0 {
|
||||
collector.Add(latency)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if latency < minLatency {
|
||||
minLatency = latency
|
||||
}
|
||||
if latency > maxLatency {
|
||||
maxLatency = latency
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(config.Duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
|
||||
totalDuration := time.Since(startTime)
|
||||
|
||||
result := BenchResult{
|
||||
TotalRequests: totalRequests,
|
||||
SuccessRequests: successRequests,
|
||||
FailedRequests: failedRequests,
|
||||
TotalDuration: totalDuration,
|
||||
MinLatency: minLatency,
|
||||
MaxLatency: maxLatency,
|
||||
P50Latency: collector.GetPercentile(0.50),
|
||||
P90Latency: collector.GetPercentile(0.90),
|
||||
P99Latency: collector.GetPercentile(0.99),
|
||||
}
|
||||
|
||||
if totalRequests > 0 {
|
||||
result.AvgLatency = totalLatency / totalRequests
|
||||
result.QPS = float64(totalRequests) / totalDuration.Seconds()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func makeRequest(client *http.Client, url string) bool {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if status, ok := result["status"].(float64); !ok || status != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func printResult(result BenchResult) {
|
||||
successRate := float64(result.SuccessRequests) / float64(result.TotalRequests) * 100
|
||||
|
||||
fmt.Printf("总请求数: %d\n", result.TotalRequests)
|
||||
fmt.Printf("成功请求: %d\n", result.SuccessRequests)
|
||||
fmt.Printf("失败请求: %d\n", result.FailedRequests)
|
||||
fmt.Printf("成功率: %.2f%%\n", successRate)
|
||||
fmt.Printf("总耗时: %v\n", result.TotalDuration.Round(time.Millisecond))
|
||||
fmt.Printf("QPS: %.2f 请求/秒\n", result.QPS)
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("最小延迟: %v\n", time.Duration(result.MinLatency).Round(time.Microsecond))
|
||||
fmt.Printf("平均延迟: %v\n", time.Duration(result.AvgLatency).Round(time.Microsecond))
|
||||
fmt.Printf("P50延迟: %v\n", time.Duration(result.P50Latency).Round(time.Microsecond))
|
||||
fmt.Printf("P90延迟: %v\n", time.Duration(result.P90Latency).Round(time.Microsecond))
|
||||
fmt.Printf("P99延迟: %v\n", time.Duration(result.P99Latency).Round(time.Microsecond))
|
||||
fmt.Printf("最大延迟: %v\n", time.Duration(result.MaxLatency).Round(time.Microsecond))
|
||||
|
||||
// 性能评级
|
||||
fmt.Print("\n性能评级: ")
|
||||
switch {
|
||||
case result.QPS >= 200000:
|
||||
fmt.Println("🏆 卓越 (QPS >= 200K)")
|
||||
case result.QPS >= 100000:
|
||||
fmt.Println("🚀 优秀 (QPS >= 100K)")
|
||||
case result.QPS >= 50000:
|
||||
fmt.Println("⭐ 良好 (QPS >= 50K)")
|
||||
case result.QPS >= 20000:
|
||||
fmt.Println("👍 中上 (QPS >= 20K)")
|
||||
case result.QPS >= 10000:
|
||||
fmt.Println("📊 中等 (QPS >= 10K)")
|
||||
default:
|
||||
fmt.Println("⚠️ 一般 (QPS < 10K)")
|
||||
}
|
||||
}
|
||||
|
||||
func estimateUsers(maxQPS float64) {
|
||||
fmt.Println("📈 并发用户数估算(基于不同使用场景):")
|
||||
fmt.Println("------------------------------------------")
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
requestInterval float64 // 用户平均请求间隔(秒)
|
||||
}{
|
||||
{"高频交互(每秒1次请求)", 1},
|
||||
{"活跃用户(每5秒1次请求)", 5},
|
||||
{"普通浏览(每10秒1次请求)", 10},
|
||||
{"低频访问(每30秒1次请求)", 30},
|
||||
{"偶尔访问(每60秒1次请求)", 60},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
users := maxQPS * s.requestInterval
|
||||
fmt.Printf("%-30s ~%d 用户\n", s.name, int(users))
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("💡 实际生产环境建议保留 30-50% 性能余量")
|
||||
fmt.Printf(" 安全并发用户数: %d - %d (普通浏览场景)\n",
|
||||
int(maxQPS*10*0.5), int(maxQPS*10*0.7))
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
fmt.Println("==========================================")
|
||||
fmt.Println(" 🔥 HoTime 极限压力测试 🔥")
|
||||
fmt.Println("==========================================")
|
||||
fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU())
|
||||
fmt.Println()
|
||||
|
||||
baseURL := "http://127.0.0.1:8081/app/test/hello"
|
||||
|
||||
// 检查服务
|
||||
fmt.Println("正在检查服务是否可用...")
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(baseURL)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
fmt.Println("❌ 服务不可用,请先启动示例应用")
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
fmt.Println("✅ 服务已就绪")
|
||||
fmt.Println()
|
||||
|
||||
// 预热
|
||||
fmt.Println("🔄 预热中 (5秒)...")
|
||||
doWarmup(baseURL, 100, 5*time.Second)
|
||||
fmt.Println("✅ 预热完成")
|
||||
fmt.Println()
|
||||
|
||||
// 极限测试配置
|
||||
concurrencyLevels := []int{500, 1000, 2000, 5000, 10000}
|
||||
testDuration := 15 * time.Second
|
||||
|
||||
var maxQPS float64
|
||||
var maxConcurrency int
|
||||
|
||||
for i, concurrency := range concurrencyLevels {
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
fmt.Printf("【极限测试 %d】并发数: %d, 持续时间: %v\n", i+1, concurrency, testDuration)
|
||||
fmt.Printf("══════════════════════════════════════════\n")
|
||||
|
||||
total, success, failed, qps, minLat, avgLat, maxLat, p50, p90, p99 := doBenchmark(baseURL, concurrency, testDuration)
|
||||
|
||||
successRate := float64(success) / float64(total) * 100
|
||||
|
||||
fmt.Printf("总请求数: %d\n", total)
|
||||
fmt.Printf("成功请求: %d\n", success)
|
||||
fmt.Printf("失败请求: %d\n", failed)
|
||||
fmt.Printf("成功率: %.2f%%\n", successRate)
|
||||
fmt.Printf("QPS: %.2f 请求/秒\n", qps)
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("最小延迟: %v\n", time.Duration(minLat))
|
||||
fmt.Printf("平均延迟: %v\n", time.Duration(avgLat))
|
||||
fmt.Printf("P50延迟: %v\n", time.Duration(p50))
|
||||
fmt.Printf("P90延迟: %v\n", time.Duration(p90))
|
||||
fmt.Printf("P99延迟: %v\n", time.Duration(p99))
|
||||
fmt.Printf("最大延迟: %v\n", time.Duration(maxLat))
|
||||
|
||||
// 性能评级
|
||||
fmt.Print("\n性能评级: ")
|
||||
switch {
|
||||
case qps >= 200000:
|
||||
fmt.Println("🏆 卓越 (QPS >= 200K)")
|
||||
case qps >= 100000:
|
||||
fmt.Println("🚀 优秀 (QPS >= 100K)")
|
||||
case qps >= 50000:
|
||||
fmt.Println("⭐ 良好 (QPS >= 50K)")
|
||||
case qps >= 20000:
|
||||
fmt.Println("👍 中上 (QPS >= 20K)")
|
||||
case qps >= 10000:
|
||||
fmt.Println("📊 中等 (QPS >= 10K)")
|
||||
default:
|
||||
fmt.Println("⚠️ 一般 (QPS < 10K)")
|
||||
}
|
||||
|
||||
if qps > maxQPS {
|
||||
maxQPS = qps
|
||||
maxConcurrency = concurrency
|
||||
}
|
||||
|
||||
// 检查是否达到瓶颈
|
||||
if successRate < 95 {
|
||||
fmt.Println("\n⚠️ 成功率低于95%,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
if avgLat > int64(100*time.Millisecond) {
|
||||
fmt.Println("\n⚠️ 平均延迟超过100ms,已达到服务极限!")
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
if i < len(concurrencyLevels)-1 {
|
||||
fmt.Println("冷却 5 秒...")
|
||||
time.Sleep(5 * time.Second)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// 最终报告
|
||||
fmt.Println()
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Println(" 📊 极限测试总结")
|
||||
fmt.Println("══════════════════════════════════════════")
|
||||
fmt.Printf("最高 QPS: %.2f 请求/秒\n", maxQPS)
|
||||
fmt.Printf("最佳并发数: %d\n", maxConcurrency)
|
||||
fmt.Println()
|
||||
|
||||
// 并发用户估算
|
||||
fmt.Println("📈 并发用户数估算:")
|
||||
fmt.Println("------------------------------------------")
|
||||
fmt.Printf("高频交互(1秒/次): ~%d 用户\n", int(maxQPS*1))
|
||||
fmt.Printf("活跃用户(5秒/次): ~%d 用户\n", int(maxQPS*5))
|
||||
fmt.Printf("普通浏览(10秒/次): ~%d 用户\n", int(maxQPS*10))
|
||||
fmt.Printf("低频访问(30秒/次): ~%d 用户\n", int(maxQPS*30))
|
||||
fmt.Println()
|
||||
fmt.Println("💡 生产环境建议保留 30-50% 性能余量")
|
||||
fmt.Printf(" 安全并发用户数: %d - %d (普通浏览场景)\n",
|
||||
int(maxQPS*10*0.5), int(maxQPS*10*0.7))
|
||||
}
|
||||
|
||||
func doWarmup(url string, concurrency int, duration time.Duration) {
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
resp, err := client.Get(url)
|
||||
if err == nil {
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func doBenchmark(url string, concurrency int, duration time.Duration) (total, success, failed int64, qps float64, minLat, avgLat, maxLat, p50, p90, p99 int64) {
|
||||
var totalLatency int64
|
||||
minLat = int64(time.Hour)
|
||||
var mu sync.Mutex
|
||||
|
||||
// 采样收集器
|
||||
latencies := make([]int64, 0, 50000)
|
||||
var latMu sync.Mutex
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: concurrency * 2,
|
||||
MaxIdleConnsPerHost: concurrency * 2,
|
||||
MaxConnsPerHost: concurrency * 2,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
startTime := time.Now()
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
reqStart := time.Now()
|
||||
ok := doRequest(client, url)
|
||||
lat := time.Since(reqStart).Nanoseconds()
|
||||
|
||||
atomic.AddInt64(&total, 1)
|
||||
atomic.AddInt64(&totalLatency, lat)
|
||||
|
||||
if ok {
|
||||
atomic.AddInt64(&success, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failed, 1)
|
||||
}
|
||||
|
||||
// 采样 (每50个采1个)
|
||||
if atomic.LoadInt64(&total)%50 == 0 {
|
||||
latMu.Lock()
|
||||
latencies = append(latencies, lat)
|
||||
latMu.Unlock()
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if lat < minLat {
|
||||
minLat = lat
|
||||
}
|
||||
if lat > maxLat {
|
||||
maxLat = lat
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(done)
|
||||
wg.Wait()
|
||||
|
||||
totalDuration := time.Since(startTime)
|
||||
|
||||
if total > 0 {
|
||||
avgLat = totalLatency / total
|
||||
qps = float64(total) / totalDuration.Seconds()
|
||||
}
|
||||
|
||||
// 计算百分位
|
||||
if len(latencies) > 0 {
|
||||
p50 = getPercentile(latencies, 0.50)
|
||||
p90 = getPercentile(latencies, 0.90)
|
||||
p99 = getPercentile(latencies, 0.99)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func doRequest(client *http.Client, url string) bool {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if status, ok := result["status"].(float64); !ok || status != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getPercentile(arr []int64, p float64) int64 {
|
||||
n := len(arr)
|
||||
if n == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 复制一份避免修改原数组
|
||||
tmp := make([]int64, n)
|
||||
copy(tmp, arr)
|
||||
|
||||
idx := int(float64(n) * p)
|
||||
if idx >= n {
|
||||
idx = n - 1
|
||||
}
|
||||
|
||||
return quickSelect(tmp, idx)
|
||||
}
|
||||
|
||||
func quickSelect(arr []int64, k int) int64 {
|
||||
if len(arr) == 1 {
|
||||
return arr[0]
|
||||
}
|
||||
|
||||
pivot := arr[len(arr)/2]
|
||||
var left, right, equal []int64
|
||||
|
||||
for _, v := range arr {
|
||||
if v < pivot {
|
||||
left = append(left, v)
|
||||
} else if v > pivot {
|
||||
right = append(right, v)
|
||||
} else {
|
||||
equal = append(equal, v)
|
||||
}
|
||||
}
|
||||
|
||||
if k < len(left) {
|
||||
return quickSelect(left, k)
|
||||
} else if k < len(left)+len(equal) {
|
||||
return pivot
|
||||
}
|
||||
return quickSelect(right, k-len(left)-len(equal))
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"cacheLongTime": 2592000,
|
||||
"cacheShortTime": 7200,
|
||||
"dbCached": 0,
|
||||
"dbHost": "127.0.0.1",
|
||||
"dbName": "test",
|
||||
"dbPort": "3306",
|
||||
"dbPwd": "root",
|
||||
"dbUser": "root",
|
||||
"debug": 1,
|
||||
"defFile": [
|
||||
"index.html",
|
||||
"index.htm"
|
||||
],
|
||||
"error": {
|
||||
"1": "内部系统异常",
|
||||
"2": "访问权限异常",
|
||||
"3": "请求参数异常",
|
||||
"4": "数据处理异常",
|
||||
"5": "数据结果异常"
|
||||
},
|
||||
"logLevel": 1,
|
||||
"modeRouterStrict": false,
|
||||
"port": "0",
|
||||
"sessionName": "HOTIME",
|
||||
"tlsCert": "",
|
||||
"tlsKey": "",
|
||||
"tlsPort": "0",
|
||||
"tpt": "tpt"
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"cacheLongTime": "两级缓存,长缓存存储时间60 * 60 * 24 * 30,一般为数据库或者redis缓存",
|
||||
"cacheShortTime": "两级缓存,短缓存存储时间60 * 60 * 2,一般为内存缓存",
|
||||
"connectLogShow": "如果需要web访问链接、访问ip、访问时间打印,0为关闭其他数字开启此功能",
|
||||
"crossDomain": "如果需要跨域设置,空字符串为不开启,*为开启所有网站允许跨域,http://www.baidu.com为指定域允许跨域",
|
||||
"dbCached": "是否开启数据库缓存0为关闭,其他开启",
|
||||
"dbHost": "数据库ip地址默认127.0.0.1",
|
||||
"dbName": "数据库名称,sqlite为文件路径比如a/x.db",
|
||||
"dbPort": "数据库端口",
|
||||
"dbPwd": "数据库密码",
|
||||
"dbType": "如果需要使用自动数据库配置,请设置此项,手动配置数据库不需要,目前支持mysql,sqlite",
|
||||
"dbUser": "数据库用户名",
|
||||
"debug": "是否开启debug模式,0关闭,其他开启,debug模式下日志展示更全",
|
||||
"defFile": "默认访问文件,默认访问index.html或者index.htm文件",
|
||||
"error": {
|
||||
"1": "内部系统异常,在环境配置,文件访问权限等基础运行环境条件不足造成严重错误时使用",
|
||||
"2": "访问权限异常,没有登录或者登录异常等时候使用",
|
||||
"3": "请求参数异常,request参数不满足要求,比如参数不足,参数类型错误,参数不满足要求等时候使用",
|
||||
"4": "数据处理异常,数据库操作或者三方请求返回的结果非正常结果,比如数据库突然中断等时候使用",
|
||||
"5": "数据结果异常,一般用于无法给出response要求的格式要求下使用,比如response需要的是string格式但你只能提供int数据时",
|
||||
"注释": "web服务内置错误提示,自定义异常建议10开始"
|
||||
},
|
||||
"logLevel": "日志等级,0打印,1关闭,2,记录到文件",
|
||||
"modeRouterStrict": "路由严格模式false,为大小写忽略必须匹配,true必须大小写匹配",
|
||||
"port": "web服务开启Http端口,0为不启用http服务",
|
||||
"redisHost": "如果需要使用redis服务时配置,默认服务ip:127.0.0.1",
|
||||
"redisPort": "如果需要使用redis服务时配置,默认服务端口:6379",
|
||||
"redisPwd": "如果需要使用redis服务时配置,默认服务密码:123456",
|
||||
"sessionName": "设置session的cookie名默认HOTIME",
|
||||
"tlsCert": "https证书",
|
||||
"tlsKey": "https密钥",
|
||||
"tlsPort": "web服务https端口,0为不启用https服务",
|
||||
"tpt": "静态文件目录,默认为程序目录下tpt目录"
|
||||
}
|
||||
+940
-84
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-3b5105e6]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-3b5105e6]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-3b5105e6]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}
|
||||
@@ -0,0 +1 @@
|
||||
body[data-v-c08f3364],dd[data-v-c08f3364],dl[data-v-c08f3364],form[data-v-c08f3364],h1[data-v-c08f3364],h2[data-v-c08f3364],h3[data-v-c08f3364],h4[data-v-c08f3364],h5[data-v-c08f3364],h6[data-v-c08f3364],html[data-v-c08f3364],ol[data-v-c08f3364],p[data-v-c08f3364],pre[data-v-c08f3364],tbody[data-v-c08f3364],textarea[data-v-c08f3364],tfoot[data-v-c08f3364],thead[data-v-c08f3364],ul[data-v-c08f3364]{margin:0;font-size:14px;font-family:Microsoft YaHei}dl[data-v-c08f3364],ol[data-v-c08f3364],ul[data-v-c08f3364]{padding:0}li[data-v-c08f3364]{list-style:none}input[data-v-c08f3364]{border:none;outline:none;font-family:Microsoft YaHei;background-color:#fff}a[data-v-c08f3364]{font-family:Microsoft YaHei;text-decoration:none}[data-v-c08f3364]{margin:0;padding:0}.login[data-v-c08f3364]{position:relative;width:100%;height:100%;background-color:#353d56;background-repeat:no-repeat;background-attachment:fixed;background-size:cover}.login-item[data-v-c08f3364]{position:absolute;top:calc(50% - 30vh);left:60%;min-width:388px;width:18vw;max-width:588px;padding:8vh 30px;box-sizing:border-box;background-size:468px 468px;background:hsla(0,0%,100%,.85);border-radius:10px}.login-item .right-content[data-v-c08f3364]{box-sizing:border-box;width:100%}.login-item .right-content .login-title[data-v-c08f3364]{font-size:26px;font-weight:700;color:#4f619b;text-align:center}.errorMsg[data-v-c08f3364]{width:100%;height:34px;line-height:34px;color:red;font-size:14px;overflow:hidden}.login-item .right-content .inputWrap[data-v-c08f3364]{width:90%;height:32px;line-height:32px;color:#646464;font-size:16px;border:1px solid #b4b4b4;margin:0 auto 5%;padding:2% 10px;border-radius:10px}.login-item .right-content .inputWrap.inputFocus[data-v-c08f3364]{border:1px solid #4f619b;box-shadow:0 0 0 3px rgba(91,113,185,.4)}.login-item .right-content .inputWrap input[data-v-c08f3364]{background-color:transparent;color:#646464;display:inline-block;height:100%;width:80%}.login-btn[data-v-c08f3364]{width:97%;height:52px;text-align:center;line-height:52px;font-size:17px;color:#fff;background-color:#4f619b;border-radius:10px;margin:0 auto;margin-top:50px;cursor:pointer;font-weight:800}
|
||||
@@ -0,0 +1 @@
|
||||
.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-51dbed3c]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-51dbed3c]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-51dbed3c]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}
|
||||
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.file-upload .el-upload{background:transparent;width:100%;height:auto;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-upload .el-upload .el-button{margin-right:10px}.el-upload img[data-v-69e31561]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-69e31561]{font-size:40px;margin:30% 31%;display:block}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}
|
||||
@@ -0,0 +1 @@
|
||||
.left-nav-home-bar{background:#2c3759!important;overflow:hidden;text-overflow:ellipsis}.left-nav-home-bar,.left-nav-home-bar i{color:#fff!important}.el-submenu .el-menu-item{height:40px;line-height:40px;width:auto;min-width:60px;padding:0 10px 0 25px!important;text-overflow:ellipsis;overflow:hidden}.el-menu .el-submenu__title{height:46px;line-height:46px;padding-left:10px!important;text-overflow:ellipsis;overflow:hidden}.left-nav-home-bar i{margin-bottom:6px!important}.el-menu-item-group__title{padding:0 0 0 10px}.el-menu--collapse .el-menu-item-group__title,.el-menu--collapse .el-submenu__title{padding-left:20px!important}.el-menu-item i,.el-submenu__title i{margin-top:-4px;vertical-align:middle;margin:-3px 5px 0 0;right:1px}.head-left[data-v-b2941c10],.head-right[data-v-b2941c10]{display:flex;justify-content:center;flex-direction:column}.head-right[data-v-b2941c10]{align-items:flex-end}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-51dbed3c]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-51dbed3c]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-51dbed3c]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}.el-dialog{margin:auto!important;top:50%;transform:translateY(-50%)}.el-dialog-div{height:75vh;overflow:auto}.el-dialog__body{padding-top:15px;padding-bottom:45px}.el-dialog-div .el-tabs__header{position:absolute;left:1px;top:69px;width:calc(90vw - 42px);margin:0 20px;z-index:1}.el-dialog-div .el-tabs__content{padding-top:50px}.el-dialog-div .el-affix--fixed{bottom:20vh}.el-dialog-div .el-affix--fixed .el-form-item{padding:0!important;background:transparent!important}
|
||||
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.file-upload .el-upload{background:transparent;width:100%;height:auto;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-upload .el-upload .el-button{margin-right:10px}.el-upload img[data-v-96cdfb38]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-96cdfb38]{font-size:40px;margin:30% 31%;display:block}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}
|
||||
@@ -0,0 +1 @@
|
||||
.not-show-tab-label .el-tabs__header{display:none}.el-descriptions__body{background:#f0f0f0}.not-show-tab-search{display:none}.el-table__body-wrapper{margin-bottom:4px;padding-bottom:2px}.el-table__body-wrapper::-webkit-scrollbar{width:8px;height:8px}.el-table__body-wrapper::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px hsla(0,0%,93.3%,.3);background-color:#eee}.el-table__body-wrapper::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(145,143,143,.3);background-color:#918f8f}.input-with-select .el-input-group__prepend{background-color:#fff}.daterange-box .select .el-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.daterange-box .daterange.el-input__inner{border-top-left-radius:0;border-bottom-left-radius:0;vertical-align:bottom}[data-v-e9f58da4] .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#409eff!important;color:#fff}
|
||||
@@ -0,0 +1 @@
|
||||
.left-nav-home-bar{background:#2c3759!important;overflow:hidden;text-overflow:ellipsis}.left-nav-home-bar,.left-nav-home-bar i{color:#fff!important}.el-submenu .el-menu-item{height:40px;line-height:40px;width:auto;min-width:60px;padding:0 10px 0 25px!important;text-overflow:ellipsis;overflow:hidden}.el-menu .el-submenu__title{height:46px;line-height:46px;padding-left:10px!important;text-overflow:ellipsis;overflow:hidden}.left-nav-home-bar i{margin-bottom:6px!important}.el-menu-item-group__title{padding:0 0 0 10px}.el-menu--collapse .el-menu-item-group__title,.el-menu--collapse .el-submenu__title{padding-left:20px!important}.el-menu-item i,.el-submenu__title i{margin-top:-4px;vertical-align:middle;margin:-3px 5px 0 0;right:1px}.head-left[data-v-b2941c10],.head-right[data-v-b2941c10]{display:flex;justify-content:center;flex-direction:column}.head-right[data-v-b2941c10]{align-items:flex-end}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.el-upload img[data-v-3b5105e6]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-3b5105e6]{font-size:40px;margin:30% 31%;display:block}.custom-tree-node[data-v-3b5105e6]{font-size:14px;padding-right:8px}.info-descriptions[data-v-4d4566fc] .el-descriptions__body .el-descriptions__label{min-width:90px;text-align:right;background:transparent;color:#606266;font-weight:400}.el-descriptions .is-bordered th[data-v-4d4566fc],.info-descriptions[data-v-4d4566fc] .el-descriptions .is-bordered td{border:transparent;max-width:25vw}.tree-line .el-tree-node{position:relative;padding-left:16px}.tree-line .el-tree-node__content{line-height:18px;font-size:14px}.tree-line .el-tree-node__children{padding-left:16px}.tree-line .el-tree-node:before{content:"";height:100%;width:1px;position:absolute;left:-3px;top:-26px;border-width:1px;border-left:1px dashed #52627c}.tree-line .el-tree-node:last-child:before{height:38px}.tree-line .el-tree-node:after{content:"";width:24px;height:20px;position:absolute;left:-3px;top:12px;border-width:1px;border-top:1px dashed #52627c}.tree-line>.el-tree-node:after{border-top:none}.tree-line>.el-tree-node:before{border-left:none}.tree-line .el-tree-node__expand-icon{font-size:18px;color:#000}.tree-line .el-tree-node__expand-icon.is-leaf{color:transparent}.dialog-box .el-descriptions__header{margin:20px 0 5px}.dialog-box .el-descriptions__body{background:#fff}.textarea-box *{word-break:break-all;white-space:pre-wrap}.textarea-box table{width:100%!important}.textarea-box img{max-width:80%!important}.textarea-box::-webkit-scrollbar-thumb{height:5px;background-color:rgba(0,0,0,.2)!important}.el-dialog{margin:auto!important;top:50%;transform:translateY(-50%)}.el-dialog-div{height:75vh;overflow:auto}.el-dialog__body{padding-top:15px;padding-bottom:45px}.el-dialog-div .el-tabs__header{position:absolute;left:1px;top:69px;width:calc(90vw - 42px);margin:0 20px;z-index:1}.el-dialog-div .el-tabs__content{padding-top:50px}.el-dialog-div .el-affix--fixed{bottom:20vh}.el-dialog-div .el-affix--fixed .el-form-item{padding:0!important;background:transparent!important}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.full-screen-container[data-v-12e6b782]{z-index:10000}.not-show-tab-label .el-tabs__header{display:none}.el-descriptions__body{background:#f0f0f0}.not-show-tab-search{display:none}.el-table__body-wrapper{margin-bottom:4px;padding-bottom:2px}.el-table__body-wrapper::-webkit-scrollbar{width:8px;height:8px}.el-table__body-wrapper::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px hsla(0,0%,93.3%,.3);background-color:#eee}.el-table__body-wrapper::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(145,143,143,.3);background-color:#918f8f}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-28
@@ -1,28 +1,3 @@
|
||||
<!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>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><script src="js/manage.js"></script><script src="https://api.map.baidu.com/api?v=2.0&ak=bF4Y6tQg94hV2vesn2ZIaUIXO4aRxxRk"></script><title></title><style>body{
|
||||
margin: 0px;
|
||||
}</style><link href="css/chunk-0de923e9.c4e8272a.css" rel="prefetch"><link href="css/chunk-1a458102.466135f0.css" rel="prefetch"><link href="css/chunk-291edee4.508cb3c8.css" rel="prefetch"><link href="css/chunk-4aefa5ec.fcc75990.css" rel="prefetch"><link href="css/chunk-4f81d902.91f1ef17.css" rel="prefetch"><link href="css/chunk-856f3c38.9b9508b8.css" rel="prefetch"><link href="css/chunk-e3f8e5a6.7876554f.css" rel="prefetch"><link href="js/chunk-0de923e9.1f0fca7e.js" rel="prefetch"><link href="js/chunk-1a458102.9a54e9ae.js" rel="prefetch"><link href="js/chunk-25ddb50b.55ec750b.js" rel="prefetch"><link href="js/chunk-291edee4.e8dbe8c8.js" rel="prefetch"><link href="js/chunk-4aefa5ec.c237610d.js" rel="prefetch"><link href="js/chunk-4f81d902.c5fdb7dd.js" rel="prefetch"><link href="js/chunk-7ea17297.38d572ef.js" rel="prefetch"><link href="js/chunk-856f3c38.29c2fcc0.js" rel="prefetch"><link href="js/chunk-e3f8e5a6.ff58e6f8.js" rel="prefetch"><link href="js/chunk-e624c1ca.62513cbc.js" rel="prefetch"><link href="css/app.abfb5de2.css" rel="preload" as="style"><link href="js/app.25e49e88.js" rel="preload" as="script"><link href="css/app.abfb5de2.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but hotime doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/app.25e49e88.js"></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-0de923e9"],{"578a":function(o,n,t){"use strict";t.r(n);t("b0c0");var i=t("f2bf"),s={class:"login-item"},r={class:"right-content"},u={class:"login-title"},l={style:{height:"60px"}},d=Object(i.r)(" 账号:"),b=Object(i.r)(" 密码:");var e=t("2934"),c={name:"Login",data:function(){return{showLog:!1,showLogInfo:"",label:"HoTime DashBoard",form:{name:"",password:""},backgroundImage:window.Hotime.data.name+"/hotime/wallpaper?random=1&type=1",focusName:!1,focusPassword:!1}},methods:{login:function(){var n=this;if(""==this.name||""==this.password)return this.showLogInfo="参数不足!",void(this.showLog=!0);Object(e.a)(window.Hotime.data.name+"/hotime/login",n.form).then(function(o){if(0!=o.status)return n.showLogInfo=o.error.msg,void(n.showLog=!0);location.hash="#/",location.reload()})},getBgImg:function(){var n=this;Object(e.e)(window.Hotime.data.name+"/hotime/wallpaper?random=1").then(function(o){0==o.status&&(n.backgroundImage=o.result.url)})},focusPrice:function(o){this["focus"+o]=!0},blurPrice:function(o){this["focus"+o]=!1}},mounted:function(){var n=this;this.label=window.Hotime.data.label,document.onkeydown=function(o){o=window.event||o;13==(o.keyCode||o.which||o.charCode)&&n.login()}}},a=(t("fad9"),t("6b0d")),t=t.n(a);n.default=t()(c,[["render",function(o,n,t,e,c,a){return Object(i.L)(),Object(i.n)("div",{class:"login",style:Object(i.C)({width:"100%",height:"100vh","background-image":"url("+c.backgroundImage+")"})},[Object(i.o)("div",s,[Object(i.o)("div",r,[Object(i.o)("p",u,Object(i.Y)(c.label),1),Object(i.o)("div",l,[Object(i.lb)(Object(i.o)("p",{class:"errorMsg"},Object(i.Y)(c.showLogInfo),513),[[i.hb,c.showLog]])]),Object(i.o)("p",{class:Object(i.B)(["inputWrap",{inputFocus:c.focusName}])},[d,Object(i.lb)(Object(i.o)("input",{type:"text","onUpdate:modelValue":n[0]||(n[0]=function(o){return c.form.name=o}),class:"accountVal",onKeyup:n[1]||(n[1]=Object(i.mb)(function(){return a.login&&a.login.apply(a,arguments)},["enter"])),onFocus:n[2]||(n[2]=function(o){return a.focusPrice("Name")}),onBlur:n[3]||(n[3]=function(o){return a.blurPrice("Name")})},null,544),[[i.gb,c.form.name]])],2),Object(i.o)("p",{class:Object(i.B)(["inputWrap",{inputFocus:c.focusPassword}])},[b,Object(i.lb)(Object(i.o)("input",{type:"password","onUpdate:modelValue":n[4]||(n[4]=function(o){return c.form.password=o}),class:"passwordVal",onKeyup:n[5]||(n[5]=Object(i.mb)(function(){return a.login&&a.login.apply(a,arguments)},["enter"])),onFocus:n[6]||(n[6]=function(o){return a.focusPrice("Password")}),onBlur:n[7]||(n[7]=function(o){return a.blurPrice("Password")})},null,544),[[i.gb,c.form.password]])],2),Object(i.o)("p",{class:"login-btn",onClick:n[8]||(n[8]=function(){return a.login&&a.login.apply(a,arguments)})},"登录")])])],4)}],["__scopeId","data-v-c08f3364"]])},dbeb:function(o,n,t){},fad9:function(o,n,t){"use strict";t("dbeb")}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-25ddb50b"],{"107c":function(t,e,n){var r=n("d039"),c=n("da84").RegExp;t.exports=r(function(){var t=c("(?<a>b)","g");return"b"!==t.exec("b").groups.a||"bc"!=="b".replace(t,"$<a>c")})},"129f":function(t,e){t.exports=Object.is||function(t,e){return t===e?0!==t||1/t==1/e:t!=t&&e!=e}},"14c3":function(t,e,n){var r=n("c6b6"),c=n("9263");t.exports=function(t,e){var n=t.exec;if("function"==typeof n){n=n.call(t,e);if("object"!=typeof n)throw TypeError("RegExp exec method returned something other than an Object or null");return n}if("RegExp"!==r(t))throw TypeError("RegExp#exec called on incompatible receiver");return c.call(t,e)}},"159b":function(t,e,n){var r,c=n("da84"),i=n("fdbc"),o=n("17c2"),a=n("9112");for(r in i){var l=c[r],l=l&&l.prototype;if(l&&l.forEach!==o)try{a(l,"forEach",o)}catch(t){l.forEach=o}}},"17c2":function(t,e,n){"use strict";var r=n("b727").forEach,n=n("a640")("forEach");t.exports=n?[].forEach:function(t){return r(this,t,1<arguments.length?arguments[1]:void 0)}},"83c5":function(t,e,n){"use strict";n("159b");e.a={list:{},constructor:function(){this.list={}},$on:function(t,e){this.list[t]=this.list[t]||[],this.list[t].push(e)},$emit:function(t,e){this.list[t]&&this.list[t].forEach(function(t){t(e)})},$off:function(t){this.list[t]&&delete this.list[t]}}},"841c":function(t,e,n){"use strict";var r=n("d784"),o=n("825a"),a=n("1d80"),l=n("129f"),s=n("577e"),u=n("14c3");r("search",function(r,c,i){return[function(t){var e=a(this),n=null==t?void 0:t[r];return void 0!==n?n.call(t,e):new RegExp(t)[r](s(e))},function(t){var e=o(this),t=s(t),n=i(c,e,t);if(n.done)return n.value;n=e.lastIndex,l(n,0)||(e.lastIndex=0),t=u(e,t);return l(e.lastIndex,n)||(e.lastIndex=n),null===t?-1:t.index}]})},9263:function(t,e,n){"use strict";var r,p=n("577e"),h=n("ad6d"),c=n("9f7f"),i=n("5692"),g=n("7c73"),v=n("69f3").get,o=n("fce3"),n=n("107c"),E=RegExp.prototype.exec,b=i("native-string-replace",String.prototype.replace),I=E,R=(i=/a/,r=/b*/g,E.call(i,"a"),E.call(r,"a"),0!==i.lastIndex||0!==r.lastIndex),y=c.UNSUPPORTED_Y||c.BROKEN_CARET,w=void 0!==/()??/.exec("")[1];(R||w||y||o||n)&&(I=function(t){var e,n,r,c,i,o,a=this,l=v(a),t=p(t),s=l.raw;if(s)return s.lastIndex=a.lastIndex,f=I.call(s,t),a.lastIndex=s.lastIndex,f;var u=l.groups,s=y&&a.sticky,f=h.call(a),l=a.source,d=0,x=t;if(s&&(-1===(f=f.replace("y","")).indexOf("g")&&(f+="g"),x=t.slice(a.lastIndex),0<a.lastIndex&&(!a.multiline||a.multiline&&"\n"!==t.charAt(a.lastIndex-1))&&(l="(?: "+l+")",x=" "+x,d++),e=new RegExp("^(?:"+l+")",f)),w&&(e=new RegExp("^"+l+"$(?!\\s)",f)),R&&(n=a.lastIndex),r=E.call(s?e:a,x),s?r?(r.input=r.input.slice(d),r[0]=r[0].slice(d),r.index=a.lastIndex,a.lastIndex+=r[0].length):a.lastIndex=0:R&&r&&(a.lastIndex=a.global?r.index+r[0].length:n),w&&r&&1<r.length&&b.call(r[0],e,function(){for(c=1;c<arguments.length-2;c++)void 0===arguments[c]&&(r[c]=void 0)}),r&&u)for(r.groups=i=g(null),c=0;c<u.length;c++)i[(o=u[c])[0]]=r[o[1]];return r}),t.exports=I},"9f7f":function(t,e,n){var r=n("d039"),c=n("da84").RegExp;e.UNSUPPORTED_Y=r(function(){var t=c("a","y");return t.lastIndex=2,null!=t.exec("abcd")}),e.BROKEN_CARET=r(function(){var t=c("^r","gy");return t.lastIndex=2,null!=t.exec("str")})},ac1f:function(t,e,n){"use strict";var r=n("23e7"),n=n("9263");r({target:"RegExp",proto:!0,forced:/./.exec!==n},{exec:n})},ad6d:function(t,e,n){"use strict";var r=n("825a");t.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.dotAll&&(e+="s"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},d784:function(t,e,n){"use strict";n("ac1f");var l=n("6eeb"),s=n("9263"),u=n("d039"),f=n("b622"),d=n("9112"),x=f("species"),p=RegExp.prototype;t.exports=function(n,t,e,r){var o,c=f(n),a=!u(function(){var t={};return t[c]=function(){return 7},7!=""[n](t)}),i=a&&!u(function(){var t=!1,e=/a/;return"split"===n&&((e={constructor:{}}).constructor[x]=function(){return e},e.flags="",e[c]=/./[c]),e.exec=function(){return t=!0,null},e[c](""),!t});a&&i&&!e||(o=/./[c],i=t(c,""[n],function(t,e,n,r,c){var i=e.exec;return i===s||i===p.exec?a&&!c?{done:!0,value:o.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}),l(String.prototype,n,i[0]),l(p,c,i[1])),r&&d(p[c],"sham",!0)}},fce3:function(t,e,n){var r=n("d039"),c=n("da84").RegExp;t.exports=r(function(){var t=c(".","s");return!(t.dotAll&&t.exec("\n")&&"s"===t.flags)})}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user