Compare commits
No commits in common. "master" and "master_v1" have entirely different histories.
@ -1,249 +0,0 @@
|
|||||||
---
|
|
||||||
name: 多数据库方言与前缀支持
|
|
||||||
overview: 为 HoTimeDB ORM 实现完整的多数据库(MySQL/PostgreSQL/SQLite)方言支持和自动表前缀功能,采用智能解析+辅助方法兜底的混合策略,保持完全向后兼容。
|
|
||||||
todos:
|
|
||||||
- id: dialect-interface
|
|
||||||
content: 扩展 Dialect 接口,添加 QuoteIdentifier 和 QuoteChar 方法
|
|
||||||
status: completed
|
|
||||||
- id: identifier-processor
|
|
||||||
content: 新建 identifier.go,实现 IdentifierProcessor 及智能解析逻辑
|
|
||||||
status: completed
|
|
||||||
- id: db-integration
|
|
||||||
content: 在 db.go 中集成处理器,添加 T() 和 C() 辅助方法
|
|
||||||
status: completed
|
|
||||||
- id: crud-update
|
|
||||||
content: 修改 crud.go 中 Select/Insert/Update/Delete/buildJoin 等方法
|
|
||||||
status: completed
|
|
||||||
- id: where-update
|
|
||||||
content: 修改 where.go 中 varCond 等条件处理方法
|
|
||||||
status: completed
|
|
||||||
- id: builder-check
|
|
||||||
content: 检查 builder.go 是否需要额外修改
|
|
||||||
status: completed
|
|
||||||
- id: testing
|
|
||||||
content: 编写测试用例验证多数据库和前缀功能
|
|
||||||
status: completed
|
|
||||||
- id: todo-1769037903242-d7aip6nh1
|
|
||||||
content: ""
|
|
||||||
status: pending
|
|
||||||
---
|
|
||||||
|
|
||||||
# HoTimeDB 多数据库方言与自动前缀支持计划(更新版)
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
1. **多数据库方言支持**:MySQL、PostgreSQL、SQLite 标识符引号自动转换
|
|
||||||
2. **自动表前缀**:主表、JOIN 表、ON/WHERE 条件中的表名自动添加前缀
|
|
||||||
3. **完全向后兼容**:用户现有写法无需修改
|
|
||||||
4. **辅助方法兜底**:边缘情况可用 `T()` / `C()` 精确控制
|
|
||||||
|
|
||||||
## 混合策略设计
|
|
||||||
|
|
||||||
### 各部分处理方式
|
|
||||||
|
|
||||||
| 位置 | 处理方式 | 准确度 | 说明 |
|
|
||||||
|
|
||||||
|------|---------|--------|------|
|
|
||||||
|
|
||||||
| 主表名 | 自动 | 100% | `Select("order")` 自动处理 |
|
|
||||||
|
|
||||||
| JOIN 表名 | 自动 | 100% | `[><]order` 中提取表名处理 |
|
|
||||||
|
|
||||||
| ON 条件字符串 | 智能解析 | ~95% | 正则匹配 `table.column` 模式 |
|
|
||||||
|
|
||||||
| WHERE 条件 Map | 自动 | 100% | Map 的 key 是结构化的 |
|
|
||||||
|
|
||||||
| SELECT 字段 | 智能解析 | ~95% | 同 ON 条件 |
|
|
||||||
|
|
||||||
### 辅助方法(兜底)
|
|
||||||
|
|
||||||
```go
|
|
||||||
db.T("order") // 返回 "`app_order`" (MySQL) 或 "\"app_order\"" (PG)
|
|
||||||
db.C("order", "name") // 返回 "`app_order`.`name`"
|
|
||||||
db.C("order.name") // 同上,支持点号格式
|
|
||||||
```
|
|
||||||
|
|
||||||
## 实现步骤
|
|
||||||
|
|
||||||
### 第1步:扩展 Dialect 接口([db/dialect.go](db/dialect.go))
|
|
||||||
|
|
||||||
添加新方法到 `Dialect` 接口:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
|
|
||||||
QuoteIdentifier(name string) string
|
|
||||||
|
|
||||||
// QuoteChar 返回引号字符
|
|
||||||
QuoteChar() string
|
|
||||||
```
|
|
||||||
|
|
||||||
三种方言实现:
|
|
||||||
|
|
||||||
- MySQL: 反引号 `` ` ``
|
|
||||||
- PostgreSQL/SQLite: 双引号 `"`
|
|
||||||
|
|
||||||
### 第2步:添加标识符处理器([db/identifier.go](db/identifier.go) 新文件)
|
|
||||||
|
|
||||||
```go
|
|
||||||
type IdentifierProcessor struct {
|
|
||||||
dialect Dialect
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessTableName 处理表名(添加前缀+引号)
|
|
||||||
// "order" → "`app_order`"
|
|
||||||
func (p *IdentifierProcessor) ProcessTableName(name string) string
|
|
||||||
|
|
||||||
// ProcessColumn 处理 table.column 格式
|
|
||||||
// "order.name" → "`app_order`.`name`"
|
|
||||||
// "`order`.name" → "`app_order`.`name`"
|
|
||||||
func (p *IdentifierProcessor) ProcessColumn(name string) string
|
|
||||||
|
|
||||||
// ProcessConditionString 智能解析条件字符串
|
|
||||||
// "user.id = order.user_id" → "`app_user`.`id` = `app_order`.`user_id`"
|
|
||||||
func (p *IdentifierProcessor) ProcessConditionString(condition string) string
|
|
||||||
|
|
||||||
// ProcessFieldList 处理字段列表字符串
|
|
||||||
// "order.id, user.name AS uname" → "`app_order`.`id`, `app_user`.`name` AS uname"
|
|
||||||
func (p *IdentifierProcessor) ProcessFieldList(fields string) string
|
|
||||||
```
|
|
||||||
|
|
||||||
**智能解析正则**:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 匹配 table.column 模式,排除已有引号、函数调用等
|
|
||||||
// 模式: \b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b
|
|
||||||
// 排除: `table`.column, "table".column, FUNC(), 123.456
|
|
||||||
```
|
|
||||||
|
|
||||||
### 第3步:在 HoTimeDB 中集成([db/db.go](db/db.go))
|
|
||||||
|
|
||||||
```go
|
|
||||||
// processor 缓存
|
|
||||||
var processorOnce sync.Once
|
|
||||||
var processor *IdentifierProcessor
|
|
||||||
|
|
||||||
// GetProcessor 获取标识符处理器
|
|
||||||
func (that *HoTimeDB) GetProcessor() *IdentifierProcessor
|
|
||||||
|
|
||||||
// T 辅助方法:获取带前缀和引号的表名
|
|
||||||
func (that *HoTimeDB) T(table string) string
|
|
||||||
|
|
||||||
// C 辅助方法:获取带前缀和引号的 table.column
|
|
||||||
func (that *HoTimeDB) C(args ...string) string
|
|
||||||
```
|
|
||||||
|
|
||||||
### 第4步:修改 CRUD 方法([db/crud.go](db/crud.go))
|
|
||||||
|
|
||||||
**Select 方法改动**:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// L112-116 原代码
|
|
||||||
if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") {
|
|
||||||
query += " FROM `" + that.Prefix + table + "` "
|
|
||||||
} else {
|
|
||||||
query += " FROM " + that.Prefix + table + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
// 改为
|
|
||||||
query += " FROM " + that.GetProcessor().ProcessTableName(table) + " "
|
|
||||||
|
|
||||||
// 字段列表处理(L90-107)
|
|
||||||
// 如果是字符串,调用 ProcessFieldList 处理
|
|
||||||
```
|
|
||||||
|
|
||||||
**buildJoin 方法改动**(L156-222):
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 原代码 L186-190
|
|
||||||
table := Substr(k, 3, len(k)-3)
|
|
||||||
if !strings.Contains(table, " ") {
|
|
||||||
table = "`" + table + "`"
|
|
||||||
}
|
|
||||||
query += " LEFT JOIN " + table + " ON " + v.(string) + " "
|
|
||||||
|
|
||||||
// 改为
|
|
||||||
table := Substr(k, 3, len(k)-3)
|
|
||||||
table = that.GetProcessor().ProcessTableName(table)
|
|
||||||
onCondition := that.GetProcessor().ProcessConditionString(v.(string))
|
|
||||||
query += " LEFT JOIN " + table + " ON " + onCondition + " "
|
|
||||||
```
|
|
||||||
|
|
||||||
**Insert/Inserts/Update/Delete** 同样修改表名和字段名处理。
|
|
||||||
|
|
||||||
### 第5步:修改 WHERE 条件处理([db/where.go](db/where.go))
|
|
||||||
|
|
||||||
**varCond 方法改动**(多处):
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 原代码(多处出现)
|
|
||||||
if !strings.Contains(k, ".") {
|
|
||||||
k = "`" + k + "`"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 改为
|
|
||||||
k = that.GetProcessor().ProcessColumn(k)
|
|
||||||
```
|
|
||||||
|
|
||||||
需要修改的函数:
|
|
||||||
|
|
||||||
- `varCond` (L205-338)
|
|
||||||
- `handleDefaultCondition` (L340-368)
|
|
||||||
- `handlePlainField` (L370-400)
|
|
||||||
|
|
||||||
### 第6步:修改链式构建器([db/builder.go](db/builder.go))
|
|
||||||
|
|
||||||
**LeftJoin 等方法需要传递处理器**:
|
|
||||||
|
|
||||||
由于 builder 持有 HoTimeDB 引用,可以直接使用:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder {
|
|
||||||
// 不在这里处理,让 buildJoin 统一处理
|
|
||||||
that.Join(Map{"[>]" + table: joinStr})
|
|
||||||
return that
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
JOIN 的实际处理在 `crud.go` 的 `buildJoin` 中完成。
|
|
||||||
|
|
||||||
## 智能解析的边界处理
|
|
||||||
|
|
||||||
### 会自动处理的情况
|
|
||||||
|
|
||||||
- `user.id = order.user_id` → 正确处理
|
|
||||||
- `user.id=order.user_id` → 正确处理(无空格)
|
|
||||||
- `` `user`.id = order.user_id `` → 正确处理(混合格式)
|
|
||||||
- `user.id = order.user_id AND order.status = 1` → 正确处理
|
|
||||||
|
|
||||||
### 需要辅助方法的边缘情况
|
|
||||||
|
|
||||||
- 子查询中的表名
|
|
||||||
- 复杂 CASE WHEN 表达式
|
|
||||||
- 动态拼接的 SQL 片段
|
|
||||||
|
|
||||||
## 文件清单
|
|
||||||
|
|
||||||
| 文件 | 操作 | 说明 |
|
|
||||||
|
|
||||||
|------|------|------|
|
|
||||||
|
|
||||||
| [db/dialect.go](db/dialect.go) | 修改 | 扩展 Dialect 接口 |
|
|
||||||
|
|
||||||
| [db/identifier.go](db/identifier.go) | 新增 | IdentifierProcessor 实现 |
|
|
||||||
|
|
||||||
| [db/db.go](db/db.go) | 修改 | 集成处理器,添加 T()/C() 方法 |
|
|
||||||
|
|
||||||
| [db/crud.go](db/crud.go) | 修改 | 修改所有 CRUD 方法 |
|
|
||||||
|
|
||||||
| [db/where.go](db/where.go) | 修改 | 修改条件处理逻辑 |
|
|
||||||
|
|
||||||
| [db/builder.go](db/builder.go) | 检查 | 可能无需修改(buildJoin 统一处理)|
|
|
||||||
|
|
||||||
## 测试用例
|
|
||||||
|
|
||||||
1. **多数据库切换**:MySQL → PostgreSQL → SQLite
|
|
||||||
2. **前缀场景**:有前缀 vs 无前缀
|
|
||||||
3. **复杂 JOIN**:多表 JOIN + 复杂 ON 条件
|
|
||||||
4. **混合写法**:`order.name` + `` `user`.id `` 混用
|
|
||||||
5. **辅助方法**:`T()` 和 `C()` 正确性
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
---
|
|
||||||
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,5 +0,0 @@
|
|||||||
{
|
|
||||||
"setup-worktree": [
|
|
||||||
"npm install"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,2 @@
|
|||||||
/.idea/*
|
/.idea/*
|
||||||
.idea
|
.idea
|
||||||
/example/tpt/demo/
|
|
||||||
*.exe
|
|
||||||
/example/config
|
|
||||||
/.cursor/*.log
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@ -142,7 +142,7 @@ any additional terms or conditions. Notwithstanding the above, nothing herein
|
|||||||
shall supersede or modify the terms of any separate license agreement you
|
shall supersede or modify the terms of any separate license agreement you
|
||||||
may have executed with Licensor regarding such Contributions.
|
may have executed with Licensor regarding such Contributions.
|
||||||
|
|
||||||
6. Trademarks. that License does not grant permission to use the trade names,
|
6. Trademarks. This License does not grant permission to use the trade names,
|
||||||
trademarks, service marks, or product names of the Licensor, except as required
|
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
|
for reasonable and customary use in describing the origin of the Work and
|
||||||
reproducing the content of the NOTICE file.
|
reproducing the content of the NOTICE file.
|
||||||
|
|||||||
90
README.md
90
README.md
@ -1,86 +1,6 @@
|
|||||||
# HoTime
|
# hotime
|
||||||
|
golang web服务框架
|
||||||
|
支持数据库db:mysql、sqlite3
|
||||||
|
支持缓存cache:redis,memory,数据库
|
||||||
|
自带工具类,上下文,以及session等功能
|
||||||
|
|
||||||
**高性能 Go Web 服务框架**
|
|
||||||
|
|
||||||
一个"小而全"的 Go Web 框架,内置 ORM、三级缓存、Session 管理,让你专注于业务逻辑。
|
|
||||||
|
|
||||||
## 核心特性
|
|
||||||
|
|
||||||
- **高性能** - 单机 10万+ QPS,支持百万级并发用户
|
|
||||||
- **内置 ORM** - 类 Medoo 语法,链式查询,支持 MySQL/SQLite/PostgreSQL
|
|
||||||
- **三级缓存** - Memory > Redis > DB,自动穿透与回填
|
|
||||||
- **Session 管理** - 内置会话管理,支持多种存储后端
|
|
||||||
- **代码生成** - 根据数据库表自动生成 CRUD 接口
|
|
||||||
- **开箱即用** - 微信支付/公众号/小程序、阿里云、腾讯云等 SDK 内置
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
| 文档 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| [快速上手指南](docs/QUICKSTART.md) | 5 分钟入门,安装配置、路由、中间件、基础数据库操作 |
|
|
||||||
| [HoTimeDB 使用说明](docs/HoTimeDB_使用说明.md) | 完整数据库 ORM 教程 |
|
|
||||||
| [HoTimeDB API 参考](docs/HoTimeDB_API参考.md) | 数据库 API 速查手册 |
|
|
||||||
| [Common 工具类](docs/Common_工具类使用说明.md) | Map/Slice/Obj 类型、类型转换、工具函数 |
|
|
||||||
| [代码生成器](docs/CodeGen_使用说明.md) | 自动 CRUD 代码生成、配置规则 |
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get code.hoteas.com/golang/hotime
|
|
||||||
```
|
|
||||||
|
|
||||||
## 性能
|
|
||||||
|
|
||||||
| 并发数 | QPS | 成功率 | 平均延迟 |
|
|
||||||
|--------|-----|--------|----------|
|
|
||||||
| 500 | 99,960 | 100% | 5.0ms |
|
|
||||||
| **1000** | **102,489** | **100%** | **9.7ms** |
|
|
||||||
| 2000 | 75,801 | 99.99% | 26.2ms |
|
|
||||||
|
|
||||||
> 测试环境:24 核 CPU,Windows 10,Go 1.19.3
|
|
||||||
|
|
||||||
### 并发用户估算
|
|
||||||
|
|
||||||
| 使用场景 | 请求频率 | 可支持用户数 |
|
|
||||||
|----------|----------|--------------|
|
|
||||||
| 高频交互 | 1次/秒 | ~10万 |
|
|
||||||
| 活跃用户 | 1次/5秒 | ~50万 |
|
|
||||||
| 普通浏览 | 1次/10秒 | ~100万 |
|
|
||||||
|
|
||||||
## 框架对比
|
|
||||||
|
|
||||||
| 特性 | HoTime | Gin | Echo | Fiber |
|
|
||||||
|------|--------|-----|------|-------|
|
|
||||||
| 性能 | 100K QPS | 70K QPS | 70K QPS | 100K QPS |
|
|
||||||
| 内置ORM | ✅ | ❌ | ❌ | ❌ |
|
|
||||||
| 内置缓存 | ✅ 三级缓存 | ❌ | ❌ | ❌ |
|
|
||||||
| Session | ✅ 内置 | ❌ 需插件 | ❌ 需插件 | ❌ 需插件 |
|
|
||||||
| 代码生成 | ✅ | ❌ | ❌ | ❌ |
|
|
||||||
| 微信/支付集成 | ✅ 内置 | ❌ | ❌ | ❌ |
|
|
||||||
|
|
||||||
## 适用场景
|
|
||||||
|
|
||||||
| 场景 | 推荐度 | 说明 |
|
|
||||||
|------|--------|------|
|
|
||||||
| 中小型后台系统 | ⭐⭐⭐⭐⭐ | 完美适配,开发效率最高 |
|
|
||||||
| 微信小程序后端 | ⭐⭐⭐⭐⭐ | 内置微信 SDK |
|
|
||||||
| 快速原型开发 | ⭐⭐⭐⭐⭐ | 代码生成 + 全功能集成 |
|
|
||||||
| 高并发 API 服务 | ⭐⭐⭐⭐ | 性能足够 |
|
|
||||||
| 大型微服务 | ⭐⭐⭐ | 建议用 Gin/Echo |
|
|
||||||
|
|
||||||
## 扩展功能
|
|
||||||
|
|
||||||
- 微信支付/公众号/小程序 - `dri/wechat/`
|
|
||||||
- 阿里云服务 - `dri/aliyun/`
|
|
||||||
- 腾讯云服务 - `dri/tencent/`
|
|
||||||
- 文件上传下载 - `dri/upload/`, `dri/download/`
|
|
||||||
- MongoDB - `dri/mongodb/`
|
|
||||||
- RSA 加解密 - `dri/rsa/`
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**HoTime** - 让 Go Web 开发更简单、更高效
|
|
||||||
|
|||||||
269
application.go
269
application.go
@ -1,14 +1,14 @@
|
|||||||
package hotime
|
package hotime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/cache"
|
. "./cache"
|
||||||
"code.hoteas.com/golang/hotime/code"
|
"./code"
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "./common"
|
||||||
. "code.hoteas.com/golang/hotime/db"
|
. "./db"
|
||||||
. "code.hoteas.com/golang/hotime/log"
|
. "./log"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -20,15 +20,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
MakeCodeRouter map[string]*code.MakeCode
|
*code.MakeCode
|
||||||
|
MakeCodeRouter Router
|
||||||
MethodRouter
|
MethodRouter
|
||||||
Router
|
Router
|
||||||
|
ContextBase
|
||||||
Error
|
Error
|
||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
WebConnectLog *logrus.Logger
|
WebConnectLog *logrus.Logger
|
||||||
Port string //端口号
|
Port string //端口号
|
||||||
TLSPort string //ssl访问端口号
|
TLSPort string //ssl访问端口号
|
||||||
connectListener []func(that *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
connectListener []func(this *Context) bool //所有的访问监听,true按原计划继续使用,false表示有监听器处理
|
||||||
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
|
connectDbFunc func(err ...*Error) (master, slave *sql.DB)
|
||||||
configPath string
|
configPath string
|
||||||
Config Map
|
Config Map
|
||||||
@ -68,25 +70,9 @@ func (that *Application) Run(router Router) {
|
|||||||
if that.Router == nil {
|
if that.Router == nil {
|
||||||
that.Router = Router{}
|
that.Router = Router{}
|
||||||
}
|
}
|
||||||
for k, _ := range router {
|
for k, v := range router {
|
||||||
v := router[k]
|
|
||||||
if that.Router[k] == nil {
|
|
||||||
that.Router[k] = v
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
//重新设置MethodRouter//直达路由
|
//重新设置MethodRouter//直达路由
|
||||||
that.MethodRouter = MethodRouter{}
|
that.MethodRouter = MethodRouter{}
|
||||||
modeRouterStrict := true
|
modeRouterStrict := true
|
||||||
@ -239,8 +225,10 @@ func (that *Application) SetConfig(configPath ...string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if that.Error.GetError() != nil {
|
that.Log = GetLog(that.Config.GetString("logFile"), true)
|
||||||
fmt.Println(that.Error.GetError().Error())
|
that.Error = Error{Logger: that.Log}
|
||||||
|
if that.Config.Get("webConnectLogShow") == nil || that.Config.GetBool("webConnectLogShow") {
|
||||||
|
that.WebConnectLog = GetLog(that.Config.GetString("webConnectLogFile"), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//文件如果损坏则不写入配置防止配置文件数据丢失
|
//文件如果损坏则不写入配置防止配置文件数据丢失
|
||||||
@ -267,25 +255,19 @@ func (that *Application) SetConfig(configPath ...string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConnectListener 连接判断,返回false继续传输至控制层,true则停止传输
|
// SetConnectListener 连接判断,返回true继续传输至控制层,false则停止传输
|
||||||
func (that *Application) SetConnectListener(lis func(that *Context) (isFinished bool)) {
|
func (that *Application) SetConnectListener(lis func(this *Context) bool) {
|
||||||
that.connectListener = append(that.connectListener, lis)
|
that.connectListener = append(that.connectListener, lis)
|
||||||
}
|
}
|
||||||
|
|
||||||
//网络错误
|
//网络错误
|
||||||
//func (that *Application) session(w http.ResponseWriter, req *http.Request) {
|
//func (this *Application) session(w http.ResponseWriter, req *http.Request) {
|
||||||
//
|
//
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// 序列化链接
|
//序列化链接
|
||||||
func (that *Application) urlSer(url string) (string, []string) {
|
func (that *Application) urlSer(url string) (string, []string) {
|
||||||
q := strings.Index(url, "?")
|
q := strings.Index(url, "?")
|
||||||
if q == -1 {
|
if q == -1 {
|
||||||
@ -331,9 +313,6 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
if len(token) == 32 {
|
if len(token) == 32 {
|
||||||
sessionId = token
|
sessionId = token
|
||||||
//没有token,则查阅session
|
//没有token,则查阅session
|
||||||
if cookie == nil || cookie.Value != sessionId {
|
|
||||||
needSetCookie = sessionId
|
|
||||||
}
|
|
||||||
} else if err == nil && cookie.Value != "" {
|
} else if err == nil && cookie.Value != "" {
|
||||||
sessionId = cookie.Value
|
sessionId = cookie.Value
|
||||||
//session也没有则判断是否创建cookie
|
//session也没有则判断是否创建cookie
|
||||||
@ -362,18 +341,14 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
//是否展示日志
|
//是否展示日志
|
||||||
if that.WebConnectLog != nil {
|
if that.WebConnectLog != nil {
|
||||||
|
ipStr := Substr(context.Req.RemoteAddr, 0, strings.Index(context.Req.RemoteAddr, ":"))
|
||||||
//负载均衡优化
|
//负载均衡优化
|
||||||
ipStr := ""
|
if ipStr == "127.0.0.1" {
|
||||||
if req.Header.Get("X-Forwarded-For") != "" {
|
if req.Header.Get("X-Forwarded-For") != "" {
|
||||||
ipStr = req.Header.Get("X-Forwarded-For")
|
ipStr = req.Header.Get("X-Forwarded-For")
|
||||||
} else if req.Header.Get("X-Real-IP") != "" {
|
} else if req.Header.Get("X-Real-IP") != "" {
|
||||||
ipStr = 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,
|
that.WebConnectLog.Infoln(ipStr, context.Req.Method,
|
||||||
@ -383,17 +358,16 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
//访问拦截true继续false暂停
|
//访问拦截true继续false暂停
|
||||||
connectListenerLen := len(that.connectListener) - 1
|
connectListenerLen := len(that.connectListener)
|
||||||
|
if connectListenerLen != 0 {
|
||||||
|
for i := 0; i < connectListenerLen; i++ {
|
||||||
|
|
||||||
|
if !that.connectListener[i](&context) {
|
||||||
|
|
||||||
for true {
|
|
||||||
if connectListenerLen < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if that.connectListener[connectListenerLen](&context) {
|
|
||||||
context.View()
|
context.View()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connectListenerLen--
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//接口服务
|
//接口服务
|
||||||
@ -446,14 +420,8 @@ func (that *Application) handler(w http.ResponseWriter, req *http.Request) {
|
|||||||
header.Set("Cache-Control", "no-cache")
|
header.Set("Cache-Control", "no-cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
t := strings.LastIndex(path, ".")
|
if strings.Index(path, ".m3u8") != -1 {
|
||||||
if t != -1 {
|
header.Add("Content-Type", "audio/mpegurl")
|
||||||
tt := path[t:]
|
|
||||||
|
|
||||||
if MimeMaps[tt] != "" {
|
|
||||||
|
|
||||||
header.Add("Content-Type", MimeMaps[tt])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//w.Write(data)
|
//w.Write(data)
|
||||||
@ -467,7 +435,6 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
|||||||
if context.Config.GetString("crossDomain") == "" {
|
if context.Config.GetString("crossDomain") == "" {
|
||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
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
|
return
|
||||||
@ -497,7 +464,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
|||||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||||
header.Set("Access-Control-Allow-Credentials", "true")
|
header.Set("Access-Control-Allow-Credentials", "true")
|
||||||
header.Set("Access-Control-Expose-Headers", "*")
|
header.Set("Access-Control-Expose-Headers", "*")
|
||||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||||
|
|
||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||||
@ -512,8 +479,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
|||||||
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
|
if (origin != "" && strings.Contains(origin, remoteHost)) || strings.Contains(refer, remoteHost) {
|
||||||
|
|
||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
//http.SetCookie(context.Resp, &http.Cookie{Name: that.Config.GetString("sessionName"), Value: sessionId, Path: "/"})
|
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
|
return
|
||||||
@ -544,7 +510,7 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
|||||||
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
header.Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE")
|
||||||
header.Set("Access-Control-Allow-Credentials", "true")
|
header.Set("Access-Control-Allow-Credentials", "true")
|
||||||
header.Set("Access-Control-Expose-Headers", "*")
|
header.Set("Access-Control-Expose-Headers", "*")
|
||||||
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token,Authorization,Cookie,Set-Cookie")
|
header.Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Access-Token")
|
||||||
|
|
||||||
if sessionId != "" {
|
if sessionId != "" {
|
||||||
//跨域允许需要设置cookie的允许跨域https才有效果
|
//跨域允许需要设置cookie的允许跨域https才有效果
|
||||||
@ -553,72 +519,41 @@ func (that *Application) crossDomain(context *Context, sessionId string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init 初始化application
|
//Init 初始化application
|
||||||
func Init(config string) *Application {
|
func Init(config string) Application {
|
||||||
appIns := Application{}
|
appIns := Application{}
|
||||||
//手动模式,
|
//手动模式,
|
||||||
appIns.SetConfig(config)
|
appIns.SetConfig(config)
|
||||||
|
|
||||||
SetDB(&appIns)
|
SetDB(&appIns)
|
||||||
appIns.SetCache()
|
appIns.SetCache()
|
||||||
codeConfig := appIns.Config.GetSlice("codeConfig")
|
appIns.MakeCode = &code.MakeCode{}
|
||||||
|
codeConfig := appIns.Config.GetMap("codeConfig")
|
||||||
if codeConfig != nil {
|
if codeConfig != nil {
|
||||||
|
|
||||||
for k, _ := range codeConfig {
|
for k, _ := range codeConfig {
|
||||||
codeMake := codeConfig.GetMap(k)
|
if appIns.Config.GetInt("mode") == 2 {
|
||||||
if codeMake == nil {
|
appIns.MakeCode.Db2JSON(k, codeConfig.GetString(k), &appIns.Db, true)
|
||||||
continue
|
} else if appIns.Config.GetInt("mode") == 3 {
|
||||||
|
appIns.MakeCode.Db2JSON(k, codeConfig.GetString(k), &appIns.Db, false)
|
||||||
|
} else {
|
||||||
|
appIns.MakeCode.Db2JSON(k, codeConfig.GetString(k), nil, false)
|
||||||
}
|
}
|
||||||
//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 {
|
if appIns.Router == nil {
|
||||||
appIns.Router = Router{}
|
appIns.Router = Router{}
|
||||||
}
|
}
|
||||||
|
appIns.Router[k] = TptProject
|
||||||
//appIns.Router[codeMake.GetString("name")] = TptProject
|
for k1, _ := range appIns.MakeCode.TableColumns {
|
||||||
appIns.Router[codeMake.GetString("name")] = Proj{}
|
appIns.Router[k][k1] = appIns.Router[k]["hotimeCommon"]
|
||||||
|
|
||||||
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 {
|
setMakeCodeLintener(k, &appIns)
|
||||||
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
|
return appIns
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDB 智能数据库设置
|
// SetDB 智能数据库设置
|
||||||
@ -638,8 +573,6 @@ func SetMysqlDB(appIns *Application, config Map) {
|
|||||||
appIns.Db.Type = "mysql"
|
appIns.Db.Type = "mysql"
|
||||||
appIns.Db.DBName = config.GetString("name")
|
appIns.Db.DBName = config.GetString("name")
|
||||||
appIns.Db.Prefix = config.GetString("prefix")
|
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) {
|
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||||
//master数据库配置
|
//master数据库配置
|
||||||
query := config.GetString("user") + ":" + config.GetString("password") +
|
query := config.GetString("user") + ":" + config.GetString("password") +
|
||||||
@ -669,8 +602,6 @@ func SetSqliteDB(appIns *Application, config Map) {
|
|||||||
|
|
||||||
appIns.Db.Type = "sqlite"
|
appIns.Db.Type = "sqlite"
|
||||||
appIns.Db.Prefix = config.GetString("prefix")
|
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) {
|
appIns.SetConnectDB(func(err ...*Error) (master, slave *sql.DB) {
|
||||||
db, e := sql.Open("sqlite3", config.GetString("path"))
|
db, e := sql.Open("sqlite3", config.GetString("path"))
|
||||||
if e != nil && len(err) != 0 {
|
if e != nil && len(err) != 0 {
|
||||||
@ -682,56 +613,81 @@ func SetSqliteDB(appIns *Application, config Map) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMakeCodeListener(name string, appIns *Application) {
|
func setMakeCodeLintener(name string, appIns *Application) {
|
||||||
appIns.SetConnectListener(func(context *Context) (isFinished bool) {
|
appIns.SetConnectListener(func(context *Context) 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 len(context.RouterString) > 1 && context.RouterString[0] == name {
|
||||||
if context.RouterString[1] == "hotime" && context.RouterString[2] == "login" {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
if context.RouterString[1] == "hotime" && context.RouterString[2] == "logout" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.Session(name+"_id").Data == nil {
|
||||||
|
context.Display(2, "你还没有登录")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//文件上传接口
|
||||||
|
if len(context.RouterString) == 1 && context.RouterString[0] == "file" && context.Req.Method == "POST" {
|
||||||
|
if context.Session(name+"_id").Data == nil {
|
||||||
|
context.Display(2, "你还没有登录")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
//读取网络文件
|
||||||
|
fi, fheader, err := context.Req.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
context.Display(3, err)
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
filePath := context.Config.GetString("filePath")
|
||||||
|
if filePath == "" {
|
||||||
|
filePath = "file/2006/01/02/"
|
||||||
|
}
|
||||||
|
|
||||||
|
path := time.Now().Format(filePath)
|
||||||
|
e := os.MkdirAll(context.Config.GetString("tpt")+"/"+path, os.ModeDir)
|
||||||
|
if e != nil {
|
||||||
|
context.Display(3, e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
filePath = path + Md5(ObjToStr(RandX(100000, 9999999))) + fheader.Filename[strings.LastIndex(fheader.Filename, "."):]
|
||||||
|
newFile, e := os.Create(context.Config.GetString("tpt") + "/" + filePath)
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
context.Display(3, e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, e = io.Copy(newFile, fi)
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
context.Display(3, e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Display(0, filePath)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(context.RouterString) < 2 || len(context.RouterString) > 3 ||
|
if len(context.RouterString) < 2 || len(context.RouterString) > 3 ||
|
||||||
!(context.Router[context.RouterString[0]] != nil &&
|
!(context.Router[context.RouterString[0]] != nil &&
|
||||||
context.Router[context.RouterString[0]][context.RouterString[1]] != nil) {
|
context.Router[context.RouterString[0]][context.RouterString[1]] != nil) {
|
||||||
return isFinished
|
return true
|
||||||
}
|
}
|
||||||
//排除无效操作
|
//排除无效操作
|
||||||
if len(context.RouterString) == 2 &&
|
if len(context.RouterString) == 2 &&
|
||||||
context.Req.Method != "GET" &&
|
context.Req.Method != "GET" &&
|
||||||
context.Req.Method != "POST" {
|
context.Req.Method != "POST" {
|
||||||
return isFinished
|
return true
|
||||||
}
|
}
|
||||||
//排除已有接口的无效操作
|
|
||||||
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 &&
|
if len(context.RouterString) == 2 &&
|
||||||
context.Req.Method == "GET" {
|
context.Req.Method == "GET" {
|
||||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil {
|
if context.Router[context.RouterString[0]][context.RouterString[1]]["search"] == nil {
|
||||||
return isFinished
|
return true
|
||||||
}
|
}
|
||||||
context.Router[context.RouterString[0]][context.RouterString[1]]["search"](context)
|
context.Router[context.RouterString[0]][context.RouterString[1]]["search"](context)
|
||||||
}
|
}
|
||||||
@ -745,24 +701,14 @@ func setMakeCodeListener(name string, appIns *Application) {
|
|||||||
}
|
}
|
||||||
if len(context.RouterString) == 3 &&
|
if len(context.RouterString) == 3 &&
|
||||||
context.Req.Method == "POST" {
|
context.Req.Method == "POST" {
|
||||||
return isFinished
|
return true
|
||||||
}
|
|
||||||
//分析
|
|
||||||
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 &&
|
if len(context.RouterString) == 3 &&
|
||||||
context.Req.Method == "GET" {
|
context.Req.Method == "GET" {
|
||||||
|
|
||||||
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
|
if context.Router[context.RouterString[0]][context.RouterString[1]]["info"] == nil {
|
||||||
return isFinished
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Router[context.RouterString[0]][context.RouterString[1]]["info"](context)
|
context.Router[context.RouterString[0]][context.RouterString[1]]["info"](context)
|
||||||
@ -787,8 +733,7 @@ func setMakeCodeListener(name string, appIns *Application) {
|
|||||||
|
|
||||||
context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context)
|
context.Router[context.RouterString[0]][context.RouterString[1]]["remove"](context)
|
||||||
}
|
}
|
||||||
|
context.View()
|
||||||
//context.View()
|
return false
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
20
cache/cache.go
vendored
20
cache/cache.go
vendored
@ -1,13 +1,12 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "../common"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HoTimeCache 可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
|
// HoTimeCache 可配置memory,db,redis,默认启用memory,默认优先级为memory>redis>db,memory与数据库缓存设置项一致,
|
||||||
// 缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
//缓存数据填充会自动反方向反哺,加入memory缓存过期将自动从redis更新,但memory永远不会更新redis,如果是集群建议不要开启memory,配置即启用
|
||||||
type HoTimeCache struct {
|
type HoTimeCache struct {
|
||||||
*Error
|
*Error
|
||||||
dbCache *CacheDb
|
dbCache *CacheDb
|
||||||
@ -93,7 +92,7 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//db缓存有
|
//redis缓存有
|
||||||
if that.dbCache != nil && that.dbCache.DbSet {
|
if that.dbCache != nil && that.dbCache.DbSet {
|
||||||
reData = that.dbCache.Cache(key, data...)
|
reData = that.dbCache.Cache(key, data...)
|
||||||
if reData.Data != nil {
|
if reData.Data != nil {
|
||||||
@ -120,7 +119,7 @@ func (that *HoTimeCache) Db(key string, data ...interface{}) *Obj {
|
|||||||
if that.redisCache != nil && that.redisCache.DbSet {
|
if that.redisCache != nil && that.redisCache.DbSet {
|
||||||
reData = that.redisCache.Cache(key, data...)
|
reData = that.redisCache.Cache(key, data...)
|
||||||
}
|
}
|
||||||
//db缓存有
|
//redis缓存有
|
||||||
if that.dbCache != nil && that.dbCache.DbSet {
|
if that.dbCache != nil && that.dbCache.DbSet {
|
||||||
reData = that.dbCache.Cache(key, data...)
|
reData = that.dbCache.Cache(key, data...)
|
||||||
}
|
}
|
||||||
@ -133,7 +132,7 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
|||||||
//内存缓存有
|
//内存缓存有
|
||||||
if that.memoryCache != nil {
|
if that.memoryCache != nil {
|
||||||
reData = that.memoryCache.Cache(key, data...)
|
reData = that.memoryCache.Cache(key, data...)
|
||||||
if reData != nil && reData.Data != nil {
|
if reData != nil {
|
||||||
return reData
|
return reData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +140,8 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
|||||||
//redis缓存有
|
//redis缓存有
|
||||||
if that.redisCache != nil {
|
if that.redisCache != nil {
|
||||||
reData = that.redisCache.Cache(key, data...)
|
reData = that.redisCache.Cache(key, data...)
|
||||||
if reData != nil && reData.Data != nil {
|
if reData.Data != nil {
|
||||||
|
|
||||||
if that.memoryCache != nil {
|
if that.memoryCache != nil {
|
||||||
that.memoryCache.Cache(key, reData.Data)
|
that.memoryCache.Cache(key, reData.Data)
|
||||||
}
|
}
|
||||||
@ -149,10 +149,10 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//db缓存有
|
//redis缓存有
|
||||||
if that.dbCache != nil {
|
if that.dbCache != nil {
|
||||||
reData = that.dbCache.Cache(key, data...)
|
reData = that.dbCache.Cache(key, data...)
|
||||||
if reData != nil && reData.Data != nil {
|
if reData.Data != nil {
|
||||||
if that.memoryCache != nil {
|
if that.memoryCache != nil {
|
||||||
that.memoryCache.Cache(key, reData.Data)
|
that.memoryCache.Cache(key, reData.Data)
|
||||||
}
|
}
|
||||||
@ -174,7 +174,7 @@ func (that *HoTimeCache) Cache(key string, data ...interface{}) *Obj {
|
|||||||
if that.redisCache != nil {
|
if that.redisCache != nil {
|
||||||
reData = that.redisCache.Cache(key, data...)
|
reData = that.redisCache.Cache(key, data...)
|
||||||
}
|
}
|
||||||
//db缓存有
|
//redis缓存有
|
||||||
if that.dbCache != nil {
|
if that.dbCache != nil {
|
||||||
reData = that.dbCache.Cache(key, data...)
|
reData = that.dbCache.Cache(key, data...)
|
||||||
}
|
}
|
||||||
|
|||||||
32
cache/cache_db.go
vendored
32
cache/cache_db.go
vendored
@ -1,7 +1,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../common"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
@ -30,14 +30,14 @@ type CacheDb struct {
|
|||||||
isInit bool
|
isInit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheDb) GetError() *Error {
|
func (this *CacheDb) GetError() *Error {
|
||||||
|
|
||||||
return that.Error
|
return this.Error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheDb) SetError(err *Error) {
|
func (this *CacheDb) SetError(err *Error) {
|
||||||
that.Error = err
|
this.Error = err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheDb) initDbTable() {
|
func (that *CacheDb) initDbTable() {
|
||||||
@ -58,7 +58,7 @@ func (that *CacheDb) initDbTable() {
|
|||||||
return
|
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")
|
_, e := that.Db.Exec("CREATE TABLE `" + that.Db.GetPrefix() + "cached` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `ckey` varchar(60) DEFAULT NULL, `cvalue` varchar(2000) DEFAULT NULL, `time` bigint(20) DEFAULT NULL, `endtime` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=198740 DEFAULT CHARSET=utf8")
|
||||||
if e.GetError() == nil {
|
if e.GetError() == nil {
|
||||||
that.isInit = true
|
that.isInit = true
|
||||||
}
|
}
|
||||||
@ -74,8 +74,8 @@ func (that *CacheDb) initDbTable() {
|
|||||||
}
|
}
|
||||||
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
|
_, e := that.Db.Exec(`CREATE TABLE "` + that.Db.GetPrefix() + `cached" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"key" TEXT(60),
|
"ckey" TEXT(60),
|
||||||
"value" TEXT(2000),
|
"cvalue" TEXT(2000),
|
||||||
"time" integer,
|
"time" integer,
|
||||||
"endtime" integer
|
"endtime" integer
|
||||||
);`)
|
);`)
|
||||||
@ -87,10 +87,10 @@ func (that *CacheDb) initDbTable() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Cache键只能为string类型
|
//获取Cache键只能为string类型
|
||||||
func (that *CacheDb) get(key string) interface{} {
|
func (that *CacheDb) get(key string) interface{} {
|
||||||
|
|
||||||
cached := that.Db.Get("cached", "*", Map{"key": key})
|
cached := that.Db.Get("cached", "*", Map{"ckey": key})
|
||||||
|
|
||||||
if cached == nil {
|
if cached == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -103,19 +103,19 @@ func (that *CacheDb) get(key string) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := Map{}
|
data := Map{}
|
||||||
data.JsonToMap(cached.GetString("value"))
|
data.JsonToMap(cached.GetString("cvalue"))
|
||||||
|
|
||||||
return data.Get("data")
|
return data.Get("data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// key value ,时间为时间戳
|
//key value ,时间为时间戳
|
||||||
func (that *CacheDb) set(key string, value interface{}, tim int64) {
|
func (that *CacheDb) set(key string, value interface{}, tim int64) {
|
||||||
|
|
||||||
bte, _ := json.Marshal(Map{"data": value})
|
bte, _ := json.Marshal(Map{"data": value})
|
||||||
|
|
||||||
num := that.Db.Update("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"key": key})
|
num := that.Db.Update("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim}, Map{"ckey": key})
|
||||||
if num == int64(0) {
|
if num == int64(0) {
|
||||||
that.Db.Insert("cached", Map{"value": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "key": key})
|
that.Db.Insert("cached", Map{"cvalue": string(bte), "time": time.Now().UnixNano(), "endtime": tim, "ckey": key})
|
||||||
}
|
}
|
||||||
|
|
||||||
//随机执行删除命令
|
//随机执行删除命令
|
||||||
@ -130,10 +130,10 @@ func (that *CacheDb) delete(key string) {
|
|||||||
//如果通配删除
|
//如果通配删除
|
||||||
if del != -1 {
|
if del != -1 {
|
||||||
key = Substr(key, 0, del)
|
key = Substr(key, 0, del)
|
||||||
that.Db.Delete("cached", Map{"key": key + "%"})
|
that.Db.Delete("cached", Map{"ckey": key + "%"})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
that.Db.Delete("cached", Map{"key": key})
|
that.Db.Delete("cached", Map{"ckey": key})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
195
cache/cache_memory.go
vendored
195
cache/cache_memory.go
vendored
@ -1,115 +1,158 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../common"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CacheMemory 基于 sync.Map 的缓存实现
|
|
||||||
type CacheMemory struct {
|
type CacheMemory struct {
|
||||||
TimeOut int64
|
TimeOut int64
|
||||||
DbSet bool
|
DbSet bool
|
||||||
SessionSet bool
|
SessionSet bool
|
||||||
|
Map
|
||||||
*Error
|
*Error
|
||||||
cache sync.Map // 替代传统的 Map
|
ContextBase
|
||||||
|
mutex *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheMemory) GetError() *Error {
|
func (this *CacheMemory) GetError() *Error {
|
||||||
|
|
||||||
return that.Error
|
return this.Error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheMemory) SetError(err *Error) {
|
func (this *CacheMemory) SetError(err *Error) {
|
||||||
that.Error = err
|
this.Error = err
|
||||||
}
|
}
|
||||||
func (c *CacheMemory) get(key string) (res *Obj) {
|
|
||||||
|
|
||||||
res = &Obj{
|
//获取Cache键只能为string类型
|
||||||
Error: *c.Error,
|
func (this *CacheMemory) get(key string) interface{} {
|
||||||
}
|
this.Error.SetError(nil)
|
||||||
value, ok := c.cache.Load(key)
|
if this.Map == nil {
|
||||||
if !ok {
|
this.Map = Map{}
|
||||||
return res // 缓存不存在
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := value.(cacheData)
|
if this.Map[key] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data := this.Map.Get(key, this.Error).(cacheData)
|
||||||
|
if this.Error.GetError() != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否过期
|
|
||||||
if data.time < time.Now().Unix() {
|
if data.time < time.Now().Unix() {
|
||||||
c.cache.Delete(key) // 删除过期缓存
|
delete(this.Map, key)
|
||||||
return res
|
return nil
|
||||||
}
|
}
|
||||||
res.Data = data.data
|
return 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()
|
|
||||||
|
|
||||||
// 随机触发刷新
|
func (this *CacheMemory) refreshMap() {
|
||||||
if x := RandX(1, 100000); x > 99950 {
|
|
||||||
c.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, k)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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 {
|
if len(data) == 0 {
|
||||||
// 读操作
|
this.mutex.RLock()
|
||||||
return c.get(key)
|
reData.Data = this.get(key)
|
||||||
|
this.mutex.RUnlock()
|
||||||
|
return reData
|
||||||
}
|
}
|
||||||
|
tim := time.Now().Unix()
|
||||||
|
|
||||||
if len(data) == 1 && data[0] == nil {
|
if len(data) == 1 && data[0] == nil {
|
||||||
// 删除操作
|
this.mutex.Lock()
|
||||||
c.delete(key)
|
this.delete(key)
|
||||||
return nil
|
this.mutex.Unlock()
|
||||||
|
return reData
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写操作
|
if len(data) == 1 {
|
||||||
expireAt := now + c.TimeOut
|
|
||||||
|
tim = tim + this.TimeOut
|
||||||
|
|
||||||
|
}
|
||||||
if len(data) == 2 {
|
if len(data) == 2 {
|
||||||
if customExpire, ok := data[1].(int64); ok {
|
this.Error.SetError(nil)
|
||||||
if customExpire > now {
|
tempt := ObjToInt64(data[1], this.Error)
|
||||||
expireAt = customExpire
|
|
||||||
} else {
|
if tempt > tim {
|
||||||
expireAt = now + customExpire
|
|
||||||
}
|
tim = tempt
|
||||||
}
|
} else if this.Error.GetError() == nil {
|
||||||
}
|
|
||||||
|
tim = tim + tempt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mutex.Lock()
|
||||||
|
this.set(key, data[0], tim)
|
||||||
|
this.mutex.Unlock()
|
||||||
|
return reData
|
||||||
|
|
||||||
c.set(key, data[0], expireAt)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
192
cache/cache_redis.go
vendored
192
cache/cache_redis.go
vendored
@ -1,10 +1,9 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../common"
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,176 +14,157 @@ type CacheRedis struct {
|
|||||||
Host string
|
Host string
|
||||||
Pwd string
|
Pwd string
|
||||||
Port int64
|
Port int64
|
||||||
pool *redis.Pool
|
conn redis.Conn
|
||||||
tag int64
|
tag int64
|
||||||
ContextBase
|
ContextBase
|
||||||
*Error
|
*Error
|
||||||
initOnce sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheRedis) GetError() *Error {
|
func (this *CacheRedis) GetError() *Error {
|
||||||
|
|
||||||
return that.Error
|
return this.Error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheRedis) SetError(err *Error) {
|
func (this *CacheRedis) SetError(err *Error) {
|
||||||
that.Error = err
|
this.Error = err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 唯一标志
|
//唯一标志
|
||||||
func (that *CacheRedis) GetTag() int64 {
|
func (this *CacheRedis) GetTag() int64 {
|
||||||
|
|
||||||
if that.tag == int64(0) {
|
if this.tag == int64(0) {
|
||||||
that.tag = time.Now().UnixNano()
|
this.tag = time.Now().UnixNano()
|
||||||
}
|
}
|
||||||
return that.tag
|
return this.tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// initPool 初始化连接池(只执行一次)
|
func (this *CacheRedis) reCon() bool {
|
||||||
func (that *CacheRedis) initPool() {
|
var err error
|
||||||
that.initOnce.Do(func() {
|
this.conn, err = redis.Dial("tcp", this.Host+":"+ObjToStr(this.Port))
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
this.conn = nil
|
||||||
|
this.Error.SetError(err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
if that.Pwd != "" {
|
|
||||||
if _, err := conn.Do("AUTH", that.Pwd); err != nil {
|
if this.Pwd != "" {
|
||||||
conn.Close()
|
_, err = this.conn.Do("AUTH", this.Pwd)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
this.conn = nil
|
||||||
|
this.Error.SetError(err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return conn, nil
|
|
||||||
},
|
return true
|
||||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
|
||||||
if time.Since(t) < time.Minute {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := c.Do("PING")
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
func (this *CacheRedis) del(key string) {
|
||||||
// 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, "*")
|
del := strings.Index(key, "*")
|
||||||
if del != -1 {
|
if del != -1 {
|
||||||
val, err := redis.Strings(conn.Do("KEYS", key))
|
val, err := redis.Strings(this.conn.Do("KEYS", key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
that.Error.SetError(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(val) == 0 {
|
this.conn.Send("MULTI")
|
||||||
return
|
for i, _ := range val {
|
||||||
}
|
this.conn.Send("DEL", val[i])
|
||||||
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 {
|
} else {
|
||||||
_, err := conn.Do("DEL", key)
|
_, err := this.conn.Do("DEL", key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
that.Error.SetError(err)
|
this.Error.SetError(err)
|
||||||
|
_, err = this.conn.Do("PING")
|
||||||
|
if err != nil {
|
||||||
|
if this.reCon() {
|
||||||
|
_, err = this.conn.Do("DEL", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// key value ,时间为时间戳
|
//key value ,时间为时间戳
|
||||||
func (that *CacheRedis) set(key string, value string, expireSeconds int64) {
|
func (this *CacheRedis) set(key string, value string, time int64) {
|
||||||
conn := that.getConn()
|
_, err := this.conn.Do("SET", key, value, "EX", ObjToStr(time))
|
||||||
if conn == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
_, err := conn.Do("SET", key, value, "EX", ObjToStr(expireSeconds))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
that.Error.SetError(err)
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheRedis) get(key string) *Obj {
|
func (this *CacheRedis) get(key string) *Obj {
|
||||||
reData := &Obj{}
|
reData := &Obj{}
|
||||||
conn := that.getConn()
|
|
||||||
if conn == nil {
|
|
||||||
return reData
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
reData.Data, err = redis.String(conn.Do("GET", key))
|
reData.Data, err = redis.String(this.conn.Do("GET", key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reData.Data = nil
|
reData.Data = nil
|
||||||
if !strings.Contains(err.Error(), "nil returned") {
|
if !strings.Contains(err.Error(), "nil returned") {
|
||||||
that.Error.SetError(err)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reData
|
return reData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
func (this *CacheRedis) Cache(key string, data ...interface{}) *Obj {
|
||||||
reData := &Obj{}
|
reData := &Obj{}
|
||||||
|
if this.conn == nil {
|
||||||
//查询缓存
|
re := this.reCon()
|
||||||
if len(data) == 0 {
|
if !re {
|
||||||
reData = that.get(key)
|
|
||||||
return reData
|
return reData
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
//查询缓存
|
||||||
|
if len(data) == 0 {
|
||||||
|
|
||||||
|
reData = this.get(key)
|
||||||
|
return reData
|
||||||
|
|
||||||
|
}
|
||||||
tim := int64(0)
|
tim := int64(0)
|
||||||
//删除缓存
|
//删除缓存
|
||||||
if len(data) == 1 && data[0] == nil {
|
if len(data) == 1 && data[0] == nil {
|
||||||
that.del(key)
|
this.del(key)
|
||||||
return reData
|
return reData
|
||||||
}
|
}
|
||||||
//添加缓存
|
//添加缓存
|
||||||
if len(data) == 1 {
|
if len(data) == 1 {
|
||||||
if that.TimeOut == 0 {
|
|
||||||
//that.Time = Config.GetInt64("cacheShortTime")
|
if this.TimeOut == 0 {
|
||||||
|
//this.Time = Config.GetInt64("cacheShortTime")
|
||||||
}
|
}
|
||||||
tim += that.TimeOut
|
|
||||||
|
tim += this.TimeOut
|
||||||
}
|
}
|
||||||
if len(data) == 2 {
|
if len(data) == 2 {
|
||||||
that.Error.SetError(nil)
|
this.Error.SetError(nil)
|
||||||
tempt := ObjToInt64(data[1], that.Error)
|
tempt := ObjToInt64(data[1], this.Error)
|
||||||
if tempt > tim {
|
if tempt > tim {
|
||||||
|
|
||||||
tim = tempt
|
tim = tempt
|
||||||
} else if that.GetError() == nil {
|
} else if this.GetError() == nil {
|
||||||
|
|
||||||
tim = tim + tempt
|
tim = tim + tempt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
that.set(key, ObjToStr(data[0]), tim)
|
this.set(key, ObjToStr(data[0]), tim)
|
||||||
|
|
||||||
return reData
|
return reData
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
4
cache/type.go
vendored
4
cache/type.go
vendored
@ -1,7 +1,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CacheIns interface {
|
type CacheIns interface {
|
||||||
@ -13,7 +13,7 @@ type CacheIns interface {
|
|||||||
Cache(key string, data ...interface{}) *Obj
|
Cache(key string, data ...interface{}) *Obj
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单条缓存数据
|
//单条缓存数据
|
||||||
type cacheData struct {
|
type cacheData struct {
|
||||||
time int64
|
time int64
|
||||||
data interface{}
|
data interface{}
|
||||||
|
|||||||
189
code/config.go
189
code/config.go
@ -1,34 +1,20 @@
|
|||||||
package code
|
package code
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Config = Map{
|
var Config = Map{
|
||||||
"name": "HoTimeDashBoard",
|
"name": "HoTimeDashBoard",
|
||||||
//"id": "2f92h3herh23rh2y8",
|
"id": "2f92h3herh23rh2y8",
|
||||||
"label": "HoTime管理平台",
|
"label": "HoTime管理平台",
|
||||||
"stop": Slice{"role", "org"}, //不更新的,同时不允许修改用户自身对应的表数据
|
|
||||||
"labelConfig": Map{
|
|
||||||
"show": "开启",
|
|
||||||
"add": "添加",
|
|
||||||
"delete": "删除",
|
|
||||||
"edit": "编辑",
|
|
||||||
"info": "查看详情",
|
|
||||||
"download": "下载清单",
|
|
||||||
},
|
|
||||||
"menus": []Map{
|
"menus": []Map{
|
||||||
//{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
|
{"label": "平台首页", "name": "HelloWorld", "icon": "el-icon-s-home"},
|
||||||
//{"label": "测试表格", "table": "table", "icon": "el-icon-suitcase"},
|
//{"label": "测试表格", "table": "table", "icon": "el-icon-suitcase"},
|
||||||
//{"label": "系统管理", "name": "setting", "icon": "el-icon-setting",
|
//{"label": "系统管理", "name": "setting", "icon": "el-icon-setting",
|
||||||
// "menus": []Map{
|
// "menus": []Map{
|
||||||
// {"label": "用户管理", "table": "user",
|
// {"label": "用户管理", "table": "user"},
|
||||||
// "default": {
|
|
||||||
// "path": "info",
|
|
||||||
// "id": "1"
|
|
||||||
// },
|
|
||||||
// "auth": ["show","edit","info","add","delete"],
|
|
||||||
// },
|
|
||||||
// {"label": "组织管理", "table": "organization"},
|
// {"label": "组织管理", "table": "organization"},
|
||||||
// {"label": "地区管理", "table": "area"},
|
// {"label": "地区管理", "table": "area"},
|
||||||
// {"label": "角色管理", "table": "role"},
|
// {"label": "角色管理", "table": "role"},
|
||||||
@ -48,7 +34,6 @@ var ColumnDataType = map[string]string{
|
|||||||
"float": "number",
|
"float": "number",
|
||||||
"double": "number",
|
"double": "number",
|
||||||
"decimal": "number",
|
"decimal": "number",
|
||||||
"integer": "number", //sqlite3
|
|
||||||
"char": "text",
|
"char": "text",
|
||||||
"text": "text",
|
"text": "text",
|
||||||
"blob": "text",
|
"blob": "text",
|
||||||
@ -59,130 +44,54 @@ var ColumnDataType = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ColumnShow struct {
|
type ColumnShow struct {
|
||||||
Name string //名称
|
Name string
|
||||||
|
List bool
|
||||||
List bool //列表权限
|
Edit bool
|
||||||
Edit bool //新增和编辑权限
|
Info bool
|
||||||
Info bool //详情权限
|
Must bool
|
||||||
Must bool //字段全匹配
|
|
||||||
Type string //空字符串表示
|
Type string //空字符串表示
|
||||||
Strict bool //name严格匹配必须是这个词才行
|
Strict bool //name严格匹配必须是这个词才行
|
||||||
}
|
}
|
||||||
|
|
||||||
var RuleConfig = []Map{
|
var ColumnNameType = []ColumnShow{
|
||||||
{"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": ""},
|
{"idcard", false, true, true, false, "", false},
|
||||||
{"name": "sn", "add": false, "list": true, "edit": false, "info": true, "must": false, "strict": false, "type": ""},
|
{"id", true, false, true, false, "", true},
|
||||||
{"name": "parent_ids", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
|
{"parent_id", true, true, true, false, "", true},
|
||||||
{"name": "index", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": true, "type": "index"},
|
//"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},
|
||||||
|
|
||||||
{"name": "parent_id", "add": true, "list": true, "edit": true, "info": true, "must": false, "true": false, "type": ""},
|
{"lat", false, true, true, false, "", false},
|
||||||
|
{"lng", false, true, true, false, "", false},
|
||||||
|
{"latitude", false, true, true, false, "", false},
|
||||||
|
{"longitude", false, true, true, false, "", false},
|
||||||
|
|
||||||
{"name": "amount", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": true, "type": "money"},
|
{"index", false, false, false, false, "index", false},
|
||||||
|
{"password", false, true, false, false, "password", false},
|
||||||
{"name": "info", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": "textArea"},
|
{"pwd", false, true, false, false, "password", false},
|
||||||
|
{"info", false, true, true, false, "", false},
|
||||||
{"name": "status", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
{"version", false, false, false, false, "", false},
|
||||||
{"name": "state", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
{"seq", false, true, true, false, "", false},
|
||||||
{"name": "sex", "add": true, "list": true, "edit": true, "info": true, "must": false, "strict": false, "type": "select"},
|
{"sort", false, true, true, false, "", false},
|
||||||
|
{"note", false, true, true, false, "", false},
|
||||||
{"name": "delete", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
|
{"description", false, true, true, false, "", false},
|
||||||
|
{"abstract", false, true, true, false, "", false},
|
||||||
{"name": "lat", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"content", false, true, true, false, "", false},
|
||||||
{"name": "lng", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"address", false, true, true, false, "", false},
|
||||||
|
{"full_name", false, true, true, false, "", false},
|
||||||
{"name": "latitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"create_time", false, false, true, false, "time", true},
|
||||||
{"name": "longitude", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"modify_time", true, false, true, false, "time", true},
|
||||||
|
{"image", false, true, true, false, "image", false},
|
||||||
{"name": "password", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
|
{"img", false, true, true, false, "image", false},
|
||||||
{"name": "pwd", "add": true, "list": false, "edit": true, "info": false, "must": false, "strict": false, "type": "password"},
|
{"icon", false, true, true, false, "image", false},
|
||||||
|
{"avatar", false, true, true, false, "image", false},
|
||||||
{"name": "version", "add": false, "list": false, "edit": false, "info": false, "must": false, "strict": false, "type": ""},
|
{"file", false, true, true, false, "file", false},
|
||||||
{"name": "seq", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"age", false, true, true, false, "", false},
|
||||||
|
{"email", false, true, true, false, "", false},
|
||||||
{"name": "sort", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"time", true, true, true, true, "time", false},
|
||||||
{"name": "note", "add": true, "list": false, "edit": true, "info": true, "must": false, "strict": false, "type": ""},
|
{"level", false, false, true, false, "", false},
|
||||||
|
{"rule", true, true, true, false, "form", false},
|
||||||
{"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},
|
|
||||||
//}
|
|
||||||
|
|||||||
967
code/makecode.go
967
code/makecode.go
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,8 @@ package code
|
|||||||
var InitTpt = `package {{name}}
|
var InitTpt = `package {{name}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime"
|
. "../../../hotime"
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../../../hotime/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ID = "{{id}}"
|
var ID = "{{id}}"
|
||||||
@ -14,32 +14,30 @@ var Project = Proj{
|
|||||||
//"user": UserCtr,
|
//"user": UserCtr,
|
||||||
{{tablesCtr}}
|
{{tablesCtr}}
|
||||||
"hotime":Ctr{
|
"hotime":Ctr{
|
||||||
"login": func(that *Context) {
|
"login": func(this *Context) {
|
||||||
|
name := this.Req.FormValue("name")
|
||||||
name := that.Req.FormValue("name")
|
password := this.Req.FormValue("password")
|
||||||
password := that.Req.FormValue("password")
|
|
||||||
if name == "" || password == "" {
|
if name == "" || password == "" {
|
||||||
that.Display(3, "参数不足")
|
this.Display(3, "参数不足")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := that.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
|
user := this.Db.Get("admin", "*", Map{"AND": Map{"OR":Map{"name": name,"phone":name}, "password": Md5(password)}})
|
||||||
if user == nil {
|
if user == nil {
|
||||||
that.Display(5, "登录失败")
|
this.Display(5, "登录失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
that.Session("admin_id", user.GetCeilInt("id"))
|
this.Session("admin_id", user.GetCeilInt("id"))
|
||||||
that.Session("admin_name", name)
|
this.Session("admin_name", name)
|
||||||
that.Display(0, that.SessionId)
|
this.Display(0, this.SessionId)
|
||||||
},
|
},
|
||||||
"logout": func(that *Context) {
|
"logout": func(this *Context) {
|
||||||
that.Session("admin_id", nil)
|
this.Session("admin_id", nil)
|
||||||
that.Session("admin_name", nil)
|
this.Session("admin_name", nil)
|
||||||
that.Display(0, "退出登录成功")
|
this.Display(0, "退出登录成功")
|
||||||
},
|
},
|
||||||
"info": func(that *Context) {
|
"info": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
|
||||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
str, inData := that.MakeCodeRouter[hotimeName].Info("admin", data, that.Db)
|
str, inData := that.MakeCode.Info("admin", data, that.Db)
|
||||||
where := Map{"id": that.Session("admin_id").ToCeilInt()}
|
where := Map{"id": that.Session("admin_id").ToCeilInt()}
|
||||||
if len(inData) ==1 {
|
if len(inData) ==1 {
|
||||||
inData["id"] =where["id"]
|
inData["id"] =where["id"]
|
||||||
@ -54,7 +52,7 @@ var Project = Proj{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for k, v := range re {
|
for k, v := range re {
|
||||||
column := that.MakeCodeRouter[hotimeName].TableColumns["admin"][k]
|
column := that.MakeCode.TableColumns["admin"][k]
|
||||||
if column == nil {
|
if column == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -71,16 +69,15 @@ var Project = Proj{
|
|||||||
var CtrTpt = `package {{name}}
|
var CtrTpt = `package {{name}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime"
|
. "../../../hotime"
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../../../hotime/common"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var {{table}}Ctr = Ctr{
|
var {{table}}Ctr = Ctr{
|
||||||
"info": func(that *Context) {
|
"info": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
|
||||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
str, inData := that.MakeCodeRouter[hotimeName].Info(that.RouterString[1], data, that.Db)
|
str, inData := that.MakeCode.Info(that.RouterString[1], data, that.Db)
|
||||||
where := Map{"id": that.RouterString[2]}
|
where := Map{"id": that.RouterString[2]}
|
||||||
|
|
||||||
if len(inData) ==1 {
|
if len(inData) ==1 {
|
||||||
@ -99,7 +96,7 @@ var {{table}}Ctr = Ctr{
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range re {
|
for k, v := range re {
|
||||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
column := that.MakeCode.TableColumns[that.RouterString[1]][k]
|
||||||
if column == nil {
|
if column == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -111,8 +108,7 @@ var {{table}}Ctr = Ctr{
|
|||||||
that.Display(0, re)
|
that.Display(0, re)
|
||||||
},
|
},
|
||||||
"add": func(that *Context) {
|
"add": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
inData := that.MakeCode.Add(that.RouterString[1], that.Req)
|
||||||
inData := that.MakeCodeRouter[hotimeName].Add(that.RouterString[1], that.Req)
|
|
||||||
if inData == nil {
|
if inData == nil {
|
||||||
that.Display(3, "请求参数不足")
|
that.Display(3, "请求参数不足")
|
||||||
return
|
return
|
||||||
@ -138,8 +134,7 @@ var {{table}}Ctr = Ctr{
|
|||||||
that.Display(0, re)
|
that.Display(0, re)
|
||||||
},
|
},
|
||||||
"update": func(that *Context) {
|
"update": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
inData := that.MakeCode.Edit(that.RouterString[1], that.Req)
|
||||||
inData := that.MakeCodeRouter[hotimeName].Edit(that.RouterString[1], that.Req)
|
|
||||||
if inData == nil {
|
if inData == nil {
|
||||||
that.Display(3, "没有找到要更新的数据")
|
that.Display(3, "没有找到要更新的数据")
|
||||||
return
|
return
|
||||||
@ -170,8 +165,7 @@ var {{table}}Ctr = Ctr{
|
|||||||
that.Display(0, re)
|
that.Display(0, re)
|
||||||
},
|
},
|
||||||
"remove": func(that *Context) {
|
"remove": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
inData := that.MakeCode.Delete(that.RouterString[1], that.Req)
|
||||||
inData := that.MakeCodeRouter[hotimeName].Delete(that.RouterString[1], that.Req)
|
|
||||||
if inData == nil {
|
if inData == nil {
|
||||||
that.Display(3, "请求参数不足")
|
that.Display(3, "请求参数不足")
|
||||||
return
|
return
|
||||||
@ -192,10 +186,10 @@ var {{table}}Ctr = Ctr{
|
|||||||
},
|
},
|
||||||
|
|
||||||
"search": func(that *Context) {
|
"search": func(that *Context) {
|
||||||
hotimeName := that.RouterString[0]
|
|
||||||
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
|
|
||||||
columnStr, leftJoin, where := that.MakeCodeRouter[hotimeName].Search(that.RouterString[1], data, that.Req, that.Db)
|
columnStr, leftJoin, where := that.MakeCode.Search(that.RouterString[1], data, that.Req, that.Db)
|
||||||
|
|
||||||
page := ObjToInt(that.Req.FormValue("page"))
|
page := ObjToInt(that.Req.FormValue("page"))
|
||||||
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
@ -214,7 +208,7 @@ var {{table}}Ctr = Ctr{
|
|||||||
|
|
||||||
for _, v := range reData {
|
for _, v := range reData {
|
||||||
for k, _ := range v {
|
for k, _ := range v {
|
||||||
column := that.MakeCodeRouter[hotimeName].TableColumns[that.RouterString[1]][k]
|
column := that.MakeCode.TableColumns[that.RouterString[1]][k]
|
||||||
if column == nil {
|
if column == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,11 @@ type ContextBase struct {
|
|||||||
tag string
|
tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 唯一标志
|
//唯一标志
|
||||||
func (that *ContextBase) GetTag() string {
|
func (this *ContextBase) GetTag() string {
|
||||||
|
|
||||||
if that.tag == "" {
|
if this.tag == "" {
|
||||||
that.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
|
this.tag = ObjToStr(time.Now().Unix()) + ":" + ObjToStr(Random())
|
||||||
}
|
}
|
||||||
return that.tag
|
return this.tag
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,28 +2,26 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error 框架层处理错误
|
// Error 框架层处理错误
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Logger *logrus.Logger
|
Logger *logrus.Logger
|
||||||
error
|
error
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *Error) GetError() error {
|
func (that *Error) GetError() error {
|
||||||
that.mu.RLock()
|
|
||||||
defer that.mu.RUnlock()
|
|
||||||
return that.error
|
return that.error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *Error) SetError(err error) {
|
func (that *Error) SetError(err error) {
|
||||||
that.mu.Lock()
|
|
||||||
that.error = err
|
that.error = err
|
||||||
that.mu.Unlock()
|
|
||||||
|
|
||||||
if that.Logger != nil && err != nil {
|
if that.Logger != nil && err != nil {
|
||||||
|
//that.Logger=log.GetLog("",false)
|
||||||
that.Logger.Warn(err)
|
that.Logger.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//安全锁
|
//安全锁
|
||||||
@ -37,43 +36,7 @@ func StrFirstToUpper(str string) string {
|
|||||||
return strings.ToUpper(first) + other
|
return strings.ToUpper(first) + other
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间转字符串,第二个参数支持1-5对应显示年月日时分秒
|
//相似度计算 ld compares two strings and returns the levenshtein distance between them.
|
||||||
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 {
|
func StrLd(s, t string, ignoreCase bool) int {
|
||||||
if ignoreCase {
|
if ignoreCase {
|
||||||
s = strings.ToLower(s)
|
s = strings.ToLower(s)
|
||||||
@ -141,7 +104,7 @@ func Substr(str string, start int, length int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IndexLastStr 获取最后出现字符串的下标
|
// IndexLastStr 获取最后出现字符串的下标
|
||||||
// return 找不到返回 -1
|
//return 找不到返回 -1
|
||||||
func IndexLastStr(str, sep string) int {
|
func IndexLastStr(str, sep string) int {
|
||||||
sepSlice := []rune(sep)
|
sepSlice := []rune(sep)
|
||||||
strSlice := []rune(str)
|
strSlice := []rune(str)
|
||||||
@ -179,7 +142,7 @@ func Md5(req string) string {
|
|||||||
return hex.EncodeToString(cipherStr)
|
return hex.EncodeToString(cipherStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rand 随机数
|
//随机数
|
||||||
func Rand(count int) int {
|
func Rand(count int) int {
|
||||||
res := Random()
|
res := Random()
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
@ -204,7 +167,7 @@ func Random() float64 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandX 随机数范围
|
//随机数范围
|
||||||
func RandX(small int, max int) int {
|
func RandX(small int, max int) int {
|
||||||
res := 0
|
res := 0
|
||||||
//随机对象
|
//随机对象
|
||||||
@ -256,7 +219,7 @@ func RandX(small int, max int) int {
|
|||||||
// GetDb()
|
// GetDb()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// DeepCopyMap 复制返回数组
|
//复制返回数组
|
||||||
func DeepCopyMap(value interface{}) interface{} {
|
func DeepCopyMap(value interface{}) interface{} {
|
||||||
if valueMap, ok := value.(Map); ok {
|
if valueMap, ok := value.(Map); ok {
|
||||||
newMap := make(Map)
|
newMap := make(Map)
|
||||||
@ -315,7 +278,7 @@ func DeepCopyMap(value interface{}) interface{} {
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// Round 浮点数四舍五入保留小数
|
//浮点数四舍五入保留小数
|
||||||
func Round(f float64, n int) float64 {
|
func Round(f float64, n int) float64 {
|
||||||
pow10_n := math.Pow10(n)
|
pow10_n := math.Pow10(n)
|
||||||
return math.Trunc((f+0.5/pow10_n)*pow10_n) / pow10_n
|
return math.Trunc((f+0.5/pow10_n)*pow10_n) / pow10_n
|
||||||
|
|||||||
128
common/map.go
128
common/map.go
@ -4,140 +4,114 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// hotime的常用map
|
//hotime的常用map
|
||||||
type Map map[string]interface{}
|
type Map map[string]interface{}
|
||||||
|
|
||||||
// 获取string
|
//获取string
|
||||||
func (that Map) GetString(key string, err ...*Error) string {
|
func (this Map) GetString(key string, err ...*Error) string {
|
||||||
|
|
||||||
if len(err) != 0 {
|
if len(err) != 0 {
|
||||||
err[0].SetError(nil)
|
err[0].SetError(nil)
|
||||||
}
|
}
|
||||||
return ObjToStr((that)[key])
|
return ObjToStr((this)[key])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *Map) Pointer() *Map {
|
func (this *Map) Pointer() *Map {
|
||||||
|
|
||||||
return that
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增加接口
|
//增加接口
|
||||||
func (that Map) Put(key string, value interface{}) {
|
func (this Map) Put(key string, value interface{}) {
|
||||||
//if that==nil{
|
//if this==nil{
|
||||||
// that=Map{}
|
// this=Map{}
|
||||||
//}
|
//}
|
||||||
that[key] = value
|
this[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除接口
|
//删除接口
|
||||||
func (that Map) Delete(key string) {
|
func (this Map) Delete(key string) {
|
||||||
delete(that, key)
|
delete(this, key)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Int
|
//获取Int
|
||||||
func (that Map) GetInt(key string, err ...*Error) int {
|
func (this Map) GetInt(key string, err ...*Error) int {
|
||||||
v := ObjToInt((that)[key], err...)
|
v := ObjToInt((this)[key], err...)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Int
|
//获取Int
|
||||||
func (that Map) GetInt64(key string, err ...*Error) int64 {
|
func (this Map) GetInt64(key string, err ...*Error) int64 {
|
||||||
v := ObjToInt64((that)[key], err...)
|
v := ObjToInt64((this)[key], err...)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取向上取整Int64
|
//获取向上取整Int64
|
||||||
func (that Map) GetCeilInt64(key string, err ...*Error) int64 {
|
func (this Map) GetCeilInt64(key string, err ...*Error) int64 {
|
||||||
v := ObjToCeilInt64((that)[key], err...)
|
v := ObjToCeilInt64((this)[key], err...)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取向上取整Int
|
//获取向上取整Int
|
||||||
func (that Map) GetCeilInt(key string, err ...*Error) int {
|
func (this Map) GetCeilInt(key string, err ...*Error) int {
|
||||||
v := ObjToCeilInt((that)[key], err...)
|
v := ObjToCeilInt((this)[key], err...)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取向上取整float64
|
//获取向上取整float64
|
||||||
func (that Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
func (this Map) GetCeilFloat64(key string, err ...*Error) float64 {
|
||||||
v := ObjToCeilFloat64((that)[key], err...)
|
v := ObjToCeilFloat64((this)[key], err...)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取Float64
|
//获取Float64
|
||||||
func (that Map) GetFloat64(key string, err ...*Error) float64 {
|
func (this Map) GetFloat64(key string, err ...*Error) float64 {
|
||||||
|
|
||||||
v := ObjToFloat64((that)[key], err...)
|
v := ObjToFloat64((this)[key], err...)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Map) GetSlice(key string, err ...*Error) Slice {
|
func (this Map) GetSlice(key string, err ...*Error) Slice {
|
||||||
|
|
||||||
//var v Slice
|
//var v Slice
|
||||||
v := ObjToSlice((that)[key], err...)
|
v := ObjToSlice((this)[key], err...)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
func (that Map) GetBool(key string, err ...*Error) bool {
|
func (this Map) GetBool(key string, err ...*Error) bool {
|
||||||
|
|
||||||
//var v Slice
|
//var v Slice
|
||||||
v := ObjToBool((that)[key], err...)
|
v := ObjToBool((this)[key], err...)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Map) GetTime(key string, err ...*Error) *time.Time {
|
func (this Map) GetMap(key string, err ...*Error) Map {
|
||||||
|
|
||||||
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
|
//var data Slice
|
||||||
|
|
||||||
v := ObjToMap((that)[key], err...)
|
v := ObjToMap((this)[key], err...)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Map) Get(key string, err ...*Error) interface{} {
|
func (this Map) Get(key string, err ...*Error) interface{} {
|
||||||
|
|
||||||
if v, ok := (that)[key]; ok {
|
if v, ok := (this)[key]; ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
e := errors.New("没有存储key及对应的数据")
|
e := errors.New("没有存储key及对应的数据")
|
||||||
@ -149,11 +123,11 @@ func (that Map) Get(key string, err ...*Error) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请传递指针过来
|
//请传递指针过来
|
||||||
func (that Map) ToStruct(stct interface{}) {
|
func (this Map) ToStruct(stct interface{}) {
|
||||||
|
|
||||||
data := reflect.ValueOf(stct).Elem()
|
data := reflect.ValueOf(stct).Elem()
|
||||||
for k, v := range that {
|
for k, v := range this {
|
||||||
ks := StrFirstToUpper(k)
|
ks := StrFirstToUpper(k)
|
||||||
dkey := data.FieldByName(ks)
|
dkey := data.FieldByName(ks)
|
||||||
if !dkey.IsValid() {
|
if !dkey.IsValid() {
|
||||||
@ -161,13 +135,13 @@ func (that Map) ToStruct(stct interface{}) {
|
|||||||
}
|
}
|
||||||
switch dkey.Type().String() {
|
switch dkey.Type().String() {
|
||||||
case "int":
|
case "int":
|
||||||
dkey.SetInt(that.GetInt64(k))
|
dkey.SetInt(this.GetInt64(k))
|
||||||
case "int64":
|
case "int64":
|
||||||
dkey.Set(reflect.ValueOf(that.GetInt64(k)))
|
dkey.Set(reflect.ValueOf(this.GetInt64(k)))
|
||||||
case "float64":
|
case "float64":
|
||||||
dkey.Set(reflect.ValueOf(that.GetFloat64(k)))
|
dkey.Set(reflect.ValueOf(this.GetFloat64(k)))
|
||||||
case "string":
|
case "string":
|
||||||
dkey.Set(reflect.ValueOf(that.GetString(k)))
|
dkey.Set(reflect.ValueOf(this.GetString(k)))
|
||||||
case "interface{}":
|
case "interface{}":
|
||||||
dkey.Set(reflect.ValueOf(v))
|
dkey.Set(reflect.ValueOf(v))
|
||||||
}
|
}
|
||||||
@ -175,13 +149,13 @@ func (that Map) ToStruct(stct interface{}) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Map) ToJsonString() string {
|
func (this Map) ToJsonString() string {
|
||||||
return ObjToStr(that)
|
return ObjToStr(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Map) JsonToMap(jsonStr string, err ...*Error) {
|
func (this Map) JsonToMap(jsonStr string, err ...*Error) {
|
||||||
e := json.Unmarshal([]byte(jsonStr), &that)
|
e := json.Unmarshal([]byte(jsonStr), &this)
|
||||||
if e != nil && len(err) != 0 {
|
if e != nil && len(err) != 0 {
|
||||||
err[0].SetError(e)
|
err[0].SetError(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import "time"
|
//对象封装方便取用
|
||||||
|
|
||||||
// 对象封装方便取用
|
|
||||||
type Obj struct {
|
type Obj struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
Error
|
Error
|
||||||
@ -20,13 +18,6 @@ func (that *Obj) ToInt(err ...Error) int {
|
|||||||
return ObjToInt(that.Data, &that.Error)
|
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 {
|
func (that *Obj) ToInt64(err ...Error) int64 {
|
||||||
if len(err) != 0 {
|
if len(err) != 0 {
|
||||||
that.Error = err[0]
|
that.Error = err[0]
|
||||||
@ -82,7 +73,7 @@ func (that *Obj) ToObj() interface{} {
|
|||||||
return that.Data
|
return that.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取向上取整Int64
|
//获取向上取整Int64
|
||||||
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
||||||
if len(err) != 0 {
|
if len(err) != 0 {
|
||||||
that.Error = *err[0]
|
that.Error = *err[0]
|
||||||
@ -92,7 +83,7 @@ func (that *Obj) ToCeilInt64(err ...*Error) int64 {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取向上取整Int
|
//获取向上取整Int
|
||||||
func (that *Obj) ToCeilInt(err ...*Error) int {
|
func (that *Obj) ToCeilInt(err ...*Error) int {
|
||||||
if len(err) != 0 {
|
if len(err) != 0 {
|
||||||
that.Error = *err[0]
|
that.Error = *err[0]
|
||||||
|
|||||||
@ -5,11 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 仅限于hotime.Slice
|
//仅限于hotime.Slice
|
||||||
func ObjToMap(obj interface{}, e ...*Error) Map {
|
func ObjToMap(obj interface{}, e ...*Error) Map {
|
||||||
var err error
|
var err error
|
||||||
var v Map
|
var v Map
|
||||||
@ -60,7 +58,7 @@ func ObjToMapArray(obj interface{}, e ...*Error) []Map {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅限于hotime.Slice
|
//仅限于hotime.Slice
|
||||||
func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
||||||
var err error
|
var err error
|
||||||
var v Slice
|
var v Slice
|
||||||
@ -98,87 +96,6 @@ func ObjToSlice(obj interface{}, e ...*Error) Slice {
|
|||||||
return v
|
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 {
|
func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
||||||
var err error
|
var err error
|
||||||
v := float64(0)
|
v := float64(0)
|
||||||
@ -218,15 +135,6 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
|||||||
err = errors.New("没有合适的转换对象!")
|
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 {
|
if len(e) != 0 {
|
||||||
e[0].SetError(err)
|
e[0].SetError(err)
|
||||||
}
|
}
|
||||||
@ -234,21 +142,21 @@ func ObjToFloat64(obj interface{}, e ...*Error) float64 {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向上取整
|
//向上取整
|
||||||
func ObjToCeilInt64(obj interface{}, e ...*Error) int64 {
|
func ObjToCeilInt64(obj interface{}, e ...*Error) int64 {
|
||||||
f := ObjToCeilFloat64(obj, e...)
|
f := ObjToCeilFloat64(obj, e...)
|
||||||
return ObjToInt64(math.Ceil(f))
|
return ObjToInt64(math.Ceil(f))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向上取整
|
//向上取整
|
||||||
func ObjToCeilFloat64(obj interface{}, e ...*Error) float64 {
|
func ObjToCeilFloat64(obj interface{}, e ...*Error) float64 {
|
||||||
f := ObjToFloat64(obj, e...)
|
f := ObjToFloat64(obj, e...)
|
||||||
return math.Ceil(f)
|
return math.Ceil(f)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向上取整
|
//向上取整
|
||||||
func ObjToCeilInt(obj interface{}, e ...*Error) int {
|
func ObjToCeilInt(obj interface{}, e ...*Error) int {
|
||||||
f := ObjToCeilFloat64(obj, e...)
|
f := ObjToCeilFloat64(obj, e...)
|
||||||
return ObjToInt(f)
|
return ObjToInt(f)
|
||||||
@ -360,7 +268,7 @@ func ObjToStr(obj interface{}) string {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为Map
|
//转换为Map
|
||||||
func StrToMap(string string) Map {
|
func StrToMap(string string) Map {
|
||||||
data := Map{}
|
data := Map{}
|
||||||
data.JsonToMap(string)
|
data.JsonToMap(string)
|
||||||
@ -368,7 +276,7 @@ func StrToMap(string string) Map {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为Slice
|
//转换为Slice
|
||||||
func StrToSlice(string string) Slice {
|
func StrToSlice(string string) Slice {
|
||||||
|
|
||||||
data := ObjToSlice(string)
|
data := ObjToSlice(string)
|
||||||
@ -376,7 +284,7 @@ func StrToSlice(string string) Slice {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||||
func StrArrayToJsonStr(a string) string {
|
func StrArrayToJsonStr(a string) string {
|
||||||
|
|
||||||
if len(a) > 2 {
|
if len(a) > 2 {
|
||||||
@ -394,7 +302,7 @@ func StrArrayToJsonStr(a string) string {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字符串数组: a1,a2,a3转["a1","a2","a3"]
|
//字符串数组: a1,a2,a3转["a1","a2","a3"]
|
||||||
func JsonStrToStrArray(a string) string {
|
func JsonStrToStrArray(a string) string {
|
||||||
//a = strings.Replace(a, `"`, "", -1)
|
//a = strings.Replace(a, `"`, "", -1)
|
||||||
if len(a) != 0 {
|
if len(a) != 0 {
|
||||||
@ -404,7 +312,7 @@ func JsonStrToStrArray(a string) string {
|
|||||||
return "," + a + ","
|
return "," + a + ","
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字符串转int
|
//字符串转int
|
||||||
func StrToInt(s string) (int, error) {
|
func StrToInt(s string) (int, error) {
|
||||||
i, err := strconv.Atoi(s)
|
i, err := strconv.Atoi(s)
|
||||||
return i, err
|
return i, err
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Slice []interface{}
|
type Slice []interface{}
|
||||||
@ -15,13 +14,6 @@ func (that Slice) GetString(key int, err ...*Error) string {
|
|||||||
return ObjToStr((that)[key])
|
return ObjToStr((that)[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that Slice) GetTime(key int, err ...*Error) *time.Time {
|
|
||||||
|
|
||||||
v := ObjToTime((that)[key], err...)
|
|
||||||
return v
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt 获取Int
|
// GetInt 获取Int
|
||||||
func (that Slice) GetInt(key int, err ...*Error) int {
|
func (that Slice) GetInt(key int, err ...*Error) int {
|
||||||
v := ObjToInt((that)[key], err...)
|
v := ObjToInt((that)[key], err...)
|
||||||
|
|||||||
145
context.go
145
context.go
@ -1,37 +1,25 @@
|
|||||||
package hotime
|
package hotime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
. "./cache"
|
||||||
|
. "./common"
|
||||||
|
. "./db"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
. "code.hoteas.com/golang/hotime/db"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
*Application
|
*Application
|
||||||
Resp http.ResponseWriter
|
Resp http.ResponseWriter
|
||||||
Req *http.Request
|
Req *http.Request
|
||||||
Log Map //日志有则创建
|
|
||||||
RouterString []string
|
RouterString []string
|
||||||
Config Map
|
Config Map
|
||||||
Db *HoTimeDB
|
Db *HoTimeDB
|
||||||
RespData Map
|
RespData Map
|
||||||
RespFunc func()
|
CacheIns
|
||||||
//CacheIns
|
|
||||||
SessionIns
|
SessionIns
|
||||||
DataSize int
|
DataSize int
|
||||||
HandlerStr string //复写请求url
|
HandlerStr string //复写请求url
|
||||||
|
|
||||||
// 请求参数缓存
|
|
||||||
reqJsonCache Map // JSON Body 缓存
|
|
||||||
reqMu sync.Once // 确保 JSON 只解析一次
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mtd 唯一标志
|
// Mtd 唯一标志
|
||||||
@ -42,7 +30,7 @@ func (that *Context) Mtd(router [3]string) Map {
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印
|
//打印
|
||||||
func (that *Context) Display(statu int, data interface{}) {
|
func (that *Context) Display(statu int, data interface{}) {
|
||||||
|
|
||||||
resp := Map{"status": statu}
|
resp := Map{"status": statu}
|
||||||
@ -69,137 +57,16 @@ func (that *Context) Display(statu int, data interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (that *Context) View() {
|
func (that *Context) View() {
|
||||||
if that.RespFunc != nil {
|
|
||||||
that.RespFunc()
|
|
||||||
}
|
|
||||||
if that.RespData == nil {
|
if that.RespData == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//创建日志
|
|
||||||
if that.Log != nil {
|
|
||||||
that.Log["time"] = time.Now().Format("2006-01-02 15:04")
|
|
||||||
if that.Session("admin_id").Data != nil {
|
|
||||||
that.Log["admin_id"] = that.Session("admin_id").ToCeilInt()
|
|
||||||
}
|
|
||||||
if that.Session("user_id").Data != nil {
|
|
||||||
that.Log["user_id"] = that.Session("user_id").ToCeilInt()
|
|
||||||
}
|
|
||||||
//负载均衡优化
|
|
||||||
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)
|
d, err := json.Marshal(that.RespData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
that.Display(1, err.Error())
|
|
||||||
that.View()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
that.DataSize = len(d)
|
that.DataSize = len(d)
|
||||||
that.RespData = nil
|
that.RespData = nil
|
||||||
that.Resp.Write(d)
|
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
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
115
db/aggregate.go
115
db/aggregate.go
@ -1,115 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
93
db/backup.go
93
db/backup.go
@ -1,93 +0,0 @@
|
|||||||
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
db/builder.go
312
db/builder.go
@ -1,312 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
679
db/crud.go
679
db/crud.go
@ -1,679 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
if len(qu) > 0 {
|
|
||||||
if reflect.ValueOf(qu[intQs]).Type().String() == "string" {
|
|
||||||
// 字段列表字符串,使用处理器处理 table.column 格式
|
|
||||||
fieldStr := qu[intQs].(string)
|
|
||||||
if fieldStr != "*" {
|
|
||||||
fieldStr = processor.ProcessFieldList(fieldStr)
|
|
||||||
}
|
|
||||||
query += " " + fieldStr
|
|
||||||
} else {
|
|
||||||
data := ObjToSlice(qu[intQs])
|
|
||||||
for i := 0; i < len(data); i++ {
|
|
||||||
k := data.GetString(i)
|
|
||||||
if strings.Contains(k, " AS ") || strings.Contains(k, ".") {
|
|
||||||
// 处理 table.column 格式
|
|
||||||
query += " " + processor.ProcessFieldList(k) + " "
|
|
||||||
} else {
|
|
||||||
// 单独的列名
|
|
||||||
query += " " + processor.ProcessColumnNoPrefix(k) + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
if i+1 != len(data) {
|
|
||||||
query = query + ", "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query += " *"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表名(添加前缀和正确的引号)
|
|
||||||
query += " FROM " + processor.ProcessTableName(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{}
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
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)
|
|
||||||
// 处理表名(添加前缀和正确的引号)
|
|
||||||
table = processor.ProcessTableName(table)
|
|
||||||
// 处理 ON 条件中的 table.column
|
|
||||||
onCondition := processor.ProcessConditionString(v.(string))
|
|
||||||
query += " LEFT JOIN " + table + " ON " + onCondition + " "
|
|
||||||
}()
|
|
||||||
case "[<]":
|
|
||||||
func() {
|
|
||||||
table := Substr(k, 3, len(k)-3)
|
|
||||||
table = processor.ProcessTableName(table)
|
|
||||||
onCondition := processor.ProcessConditionString(v.(string))
|
|
||||||
query += " RIGHT JOIN " + table + " ON " + onCondition + " "
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
switch Substr(k, 0, 4) {
|
|
||||||
case "[<>]":
|
|
||||||
func() {
|
|
||||||
table := Substr(k, 4, len(k)-4)
|
|
||||||
table = processor.ProcessTableName(table)
|
|
||||||
onCondition := processor.ProcessConditionString(v.(string))
|
|
||||||
query += " FULL JOIN " + table + " ON " + onCondition + " "
|
|
||||||
}()
|
|
||||||
case "[><]":
|
|
||||||
func() {
|
|
||||||
table := Substr(k, 4, len(k)-4)
|
|
||||||
table = processor.ProcessTableName(table)
|
|
||||||
onCondition := processor.ProcessConditionString(v.(string))
|
|
||||||
query += " INNER JOIN " + table + " ON " + onCondition + " "
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get 获取单条记录
|
|
||||||
func (that *HoTimeDB) Get(table string, qu ...interface{}) Map {
|
|
||||||
if len(qu) == 0 {
|
|
||||||
// 没有参数时,添加默认字段和 LIMIT
|
|
||||||
qu = append(qu, "*", Map{"LIMIT": 1})
|
|
||||||
} else if len(qu) == 1 {
|
|
||||||
qu = append(qu, Map{"LIMIT": 1})
|
|
||||||
} else if len(qu) == 2 {
|
|
||||||
temp := qu[1].(Map)
|
|
||||||
temp["LIMIT"] = 1
|
|
||||||
qu[1] = temp
|
|
||||||
} else 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 := " ("
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
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 += processor.ProcessColumnNoPrefix(k) + ","
|
|
||||||
valueString += vstr + ","
|
|
||||||
} else {
|
|
||||||
queryString += processor.ProcessColumnNoPrefix(k) + ") "
|
|
||||||
valueString += vstr + ");"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
values = append(values, v)
|
|
||||||
if tempLen < lens {
|
|
||||||
queryString += processor.ProcessColumnNoPrefix(k) + ","
|
|
||||||
valueString += "?,"
|
|
||||||
} else {
|
|
||||||
queryString += processor.ProcessColumnNoPrefix(k) + ") "
|
|
||||||
valueString += "?);"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := "INSERT INTO " + processor.ProcessTableName(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inserts 批量插入数据
|
|
||||||
// table: 表名
|
|
||||||
// dataList: 数据列表,每个元素是一个 Map
|
|
||||||
// 返回受影响的行数
|
|
||||||
//
|
|
||||||
// 示例:
|
|
||||||
//
|
|
||||||
// affected := db.Inserts("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) Inserts(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)
|
|
||||||
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
// 构建列名部分
|
|
||||||
quotedCols := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quotedCols[i] = processor.ProcessColumnNoPrefix(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 " + processor.ProcessTableName(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)
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
quotedCols := make([]string, len(columns))
|
|
||||||
valueParts := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quotedCols[i] = processor.ProcessColumnNoPrefix(col)
|
|
||||||
if raw, ok := rawValues[col]; ok {
|
|
||||||
valueParts[i] = raw
|
|
||||||
} else {
|
|
||||||
valueParts[i] = "?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateParts := make([]string, len(updateColumns))
|
|
||||||
for i, col := range updateColumns {
|
|
||||||
quotedCol := processor.ProcessColumnNoPrefix(col)
|
|
||||||
if raw, ok := rawValues[col]; ok {
|
|
||||||
updateParts[i] = quotedCol + " = " + raw
|
|
||||||
} else {
|
|
||||||
updateParts[i] = quotedCol + " = VALUES(" + quotedCol + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "INSERT INTO " + processor.ProcessTableName(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
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
dialect := that.GetDialect()
|
|
||||||
|
|
||||||
quotedCols := make([]string, len(columns))
|
|
||||||
valueParts := make([]string, len(columns))
|
|
||||||
paramIndex := 1
|
|
||||||
for i, col := range columns {
|
|
||||||
quotedCols[i] = dialect.QuoteIdentifier(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] = dialect.QuoteIdentifier(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateParts := make([]string, len(updateColumns))
|
|
||||||
for i, col := range updateColumns {
|
|
||||||
quotedCol := dialect.QuoteIdentifier(col)
|
|
||||||
if raw, ok := rawValues[col]; ok {
|
|
||||||
updateParts[i] = quotedCol + " = " + raw
|
|
||||||
} else {
|
|
||||||
updateParts[i] = quotedCol + " = EXCLUDED." + quotedCol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "INSERT INTO " + processor.ProcessTableName(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
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
dialect := that.GetDialect()
|
|
||||||
|
|
||||||
quotedCols := make([]string, len(columns))
|
|
||||||
valueParts := make([]string, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
quotedCols[i] = dialect.QuoteIdentifier(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] = dialect.QuoteIdentifier(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateParts := make([]string, len(updateColumns))
|
|
||||||
for i, col := range updateColumns {
|
|
||||||
quotedCol := dialect.QuoteIdentifier(col)
|
|
||||||
if raw, ok := rawValues[col]; ok {
|
|
||||||
updateParts[i] = quotedCol + " = " + raw
|
|
||||||
} else {
|
|
||||||
updateParts[i] = quotedCol + " = excluded." + quotedCol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "INSERT INTO " + processor.ProcessTableName(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 {
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
query := "UPDATE " + processor.ProcessTableName(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 += processor.ProcessColumnNoPrefix(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 {
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
query := "DELETE FROM " + processor.ProcessTableName(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
|
|
||||||
}
|
|
||||||
147
db/db.go
147
db/db.go
@ -1,147 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.hoteas.com/golang/hotime/cache"
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetProcessor 获取标识符处理器
|
|
||||||
// 用于处理表名、字段名的前缀添加和引号转换
|
|
||||||
func (that *HoTimeDB) GetProcessor() *IdentifierProcessor {
|
|
||||||
return NewIdentifierProcessor(that.GetDialect(), that.Prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// T 辅助方法:获取带前缀和引号的表名
|
|
||||||
// 用于手动构建 SQL 时使用
|
|
||||||
// 示例: db.T("order") 返回 "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL)
|
|
||||||
func (that *HoTimeDB) T(table string) string {
|
|
||||||
return that.GetProcessor().ProcessTableName(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
// C 辅助方法:获取带前缀和引号的 table.column
|
|
||||||
// 支持两种调用方式:
|
|
||||||
// - db.C("order", "name") 返回 "`app_order`.`name`"
|
|
||||||
// - db.C("order.name") 返回 "`app_order`.`name`"
|
|
||||||
func (that *HoTimeDB) C(args ...string) string {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if len(args) == 1 {
|
|
||||||
return that.GetProcessor().ProcessColumn(args[0])
|
|
||||||
}
|
|
||||||
// 两个参数: table, column
|
|
||||||
dialect := that.GetDialect()
|
|
||||||
table := args[0]
|
|
||||||
column := args[1]
|
|
||||||
// 去除已有引号
|
|
||||||
table = trimQuotes(table)
|
|
||||||
column = trimQuotes(column)
|
|
||||||
return dialect.QuoteIdentifier(that.Prefix+table) + "." + dialect.QuoteIdentifier(column)
|
|
||||||
}
|
|
||||||
|
|
||||||
// trimQuotes 去除字符串两端的引号
|
|
||||||
func trimQuotes(s string) string {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if len(s) >= 2 {
|
|
||||||
if (s[0] == '`' && s[len(s)-1] == '`') || (s[0] == '"' && s[len(s)-1] == '"') {
|
|
||||||
return s[1 : len(s)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
288
db/dialect.go
288
db/dialect.go
@ -1,288 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dialect 数据库方言接口
|
|
||||||
// 用于处理不同数据库之间的语法差异
|
|
||||||
type Dialect interface {
|
|
||||||
// Quote 对表名/字段名添加引号
|
|
||||||
// MySQL 使用反引号 `name`
|
|
||||||
// PostgreSQL 使用双引号 "name"
|
|
||||||
// SQLite 使用双引号或方括号 "name" 或 [name]
|
|
||||||
Quote(name string) string
|
|
||||||
|
|
||||||
// QuoteIdentifier 处理单个标识符(去除已有引号,添加正确引号)
|
|
||||||
// 输入可能带有反引号或双引号,会先去除再添加正确格式
|
|
||||||
QuoteIdentifier(name string) string
|
|
||||||
|
|
||||||
// QuoteChar 返回引号字符
|
|
||||||
// MySQL: `
|
|
||||||
// PostgreSQL/SQLite: "
|
|
||||||
QuoteChar() 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) QuoteIdentifier(name string) string {
|
|
||||||
// 去除已有的引号(反引号和双引号)
|
|
||||||
name = strings.Trim(name, "`\"")
|
|
||||||
return "`" + name + "`"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *MySQLDialect) QuoteChar() string {
|
|
||||||
return "`"
|
|
||||||
}
|
|
||||||
|
|
||||||
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) QuoteIdentifier(name string) string {
|
|
||||||
// 去除已有的引号(反引号和双引号)
|
|
||||||
name = strings.Trim(name, "`\"")
|
|
||||||
return "\"" + name + "\""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *PostgreSQLDialect) QuoteChar() string {
|
|
||||||
return "\""
|
|
||||||
}
|
|
||||||
|
|
||||||
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) QuoteIdentifier(name string) string {
|
|
||||||
// 去除已有的引号(反引号和双引号)
|
|
||||||
name = strings.Trim(name, "`\"")
|
|
||||||
return "\"" + name + "\""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *SQLiteDialect) QuoteChar() string {
|
|
||||||
return "\""
|
|
||||||
}
|
|
||||||
|
|
||||||
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, ", "))
|
|
||||||
}
|
|
||||||
@ -1,441 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestDialectQuoteIdentifier 测试方言的 QuoteIdentifier 方法
|
|
||||||
func TestDialectQuoteIdentifier(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
dialect Dialect
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// MySQL 方言测试
|
|
||||||
{"MySQL simple", &MySQLDialect{}, "name", "`name`"},
|
|
||||||
{"MySQL with backticks", &MySQLDialect{}, "`name`", "`name`"},
|
|
||||||
{"MySQL with quotes", &MySQLDialect{}, "\"name\"", "`name`"},
|
|
||||||
|
|
||||||
// PostgreSQL 方言测试
|
|
||||||
{"PostgreSQL simple", &PostgreSQLDialect{}, "name", "\"name\""},
|
|
||||||
{"PostgreSQL with backticks", &PostgreSQLDialect{}, "`name`", "\"name\""},
|
|
||||||
{"PostgreSQL with quotes", &PostgreSQLDialect{}, "\"name\"", "\"name\""},
|
|
||||||
|
|
||||||
// SQLite 方言测试
|
|
||||||
{"SQLite simple", &SQLiteDialect{}, "name", "\"name\""},
|
|
||||||
{"SQLite with backticks", &SQLiteDialect{}, "`name`", "\"name\""},
|
|
||||||
{"SQLite with quotes", &SQLiteDialect{}, "\"name\"", "\"name\""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := tt.dialect.QuoteIdentifier(tt.input)
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("QuoteIdentifier(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDialectQuoteChar 测试方言的 QuoteChar 方法
|
|
||||||
func TestDialectQuoteChar(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
dialect Dialect
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"MySQL", &MySQLDialect{}, "`"},
|
|
||||||
{"PostgreSQL", &PostgreSQLDialect{}, "\""},
|
|
||||||
{"SQLite", &SQLiteDialect{}, "\""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := tt.dialect.QuoteChar()
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("QuoteChar() = %q, want %q", result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIdentifierProcessorTableName 测试表名处理
|
|
||||||
func TestIdentifierProcessorTableName(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
dialect Dialect
|
|
||||||
prefix string
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// MySQL 无前缀
|
|
||||||
{"MySQL no prefix", &MySQLDialect{}, "", "order", "`order`"},
|
|
||||||
{"MySQL no prefix with backticks", &MySQLDialect{}, "", "`order`", "`order`"},
|
|
||||||
|
|
||||||
// MySQL 有前缀
|
|
||||||
{"MySQL with prefix", &MySQLDialect{}, "app_", "order", "`app_order`"},
|
|
||||||
{"MySQL with prefix and backticks", &MySQLDialect{}, "app_", "`order`", "`app_order`"},
|
|
||||||
|
|
||||||
// PostgreSQL 无前缀
|
|
||||||
{"PostgreSQL no prefix", &PostgreSQLDialect{}, "", "order", "\"order\""},
|
|
||||||
|
|
||||||
// PostgreSQL 有前缀
|
|
||||||
{"PostgreSQL with prefix", &PostgreSQLDialect{}, "app_", "order", "\"app_order\""},
|
|
||||||
{"PostgreSQL with prefix and quotes", &PostgreSQLDialect{}, "app_", "\"order\"", "\"app_order\""},
|
|
||||||
|
|
||||||
// SQLite 有前缀
|
|
||||||
{"SQLite with prefix", &SQLiteDialect{}, "app_", "user", "\"app_user\""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
|
||||||
result := processor.ProcessTableName(tt.input)
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("ProcessTableName(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIdentifierProcessorColumn 测试列名处理(包括 table.column 格式)
|
|
||||||
func TestIdentifierProcessorColumn(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
dialect Dialect
|
|
||||||
prefix string
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// 单独列名
|
|
||||||
{"MySQL simple column", &MySQLDialect{}, "", "name", "`name`"},
|
|
||||||
{"MySQL simple column with prefix", &MySQLDialect{}, "app_", "name", "`name`"},
|
|
||||||
|
|
||||||
// table.column 格式
|
|
||||||
{"MySQL table.column no prefix", &MySQLDialect{}, "", "order.name", "`order`.`name`"},
|
|
||||||
{"MySQL table.column with prefix", &MySQLDialect{}, "app_", "order.name", "`app_order`.`name`"},
|
|
||||||
{"MySQL table.column with backticks", &MySQLDialect{}, "app_", "`order`.name", "`app_order`.`name`"},
|
|
||||||
|
|
||||||
// PostgreSQL
|
|
||||||
{"PostgreSQL table.column with prefix", &PostgreSQLDialect{}, "app_", "order.name", "\"app_order\".\"name\""},
|
|
||||||
{"PostgreSQL table.column with quotes", &PostgreSQLDialect{}, "app_", "\"order\".name", "\"app_order\".\"name\""},
|
|
||||||
|
|
||||||
// SQLite
|
|
||||||
{"SQLite table.column with prefix", &SQLiteDialect{}, "app_", "user.email", "\"app_user\".\"email\""},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
|
||||||
result := processor.ProcessColumn(tt.input)
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("ProcessColumn(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIdentifierProcessorConditionString 测试条件字符串处理
|
|
||||||
func TestIdentifierProcessorConditionString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
dialect Dialect
|
|
||||||
prefix string
|
|
||||||
input string
|
|
||||||
contains []string // 结果应该包含这些字符串
|
|
||||||
}{
|
|
||||||
// MySQL 简单条件
|
|
||||||
{
|
|
||||||
"MySQL simple condition",
|
|
||||||
&MySQLDialect{},
|
|
||||||
"app_",
|
|
||||||
"user.id = order.user_id",
|
|
||||||
[]string{"`app_user`", "`app_order`"},
|
|
||||||
},
|
|
||||||
// MySQL 复杂条件
|
|
||||||
{
|
|
||||||
"MySQL complex condition",
|
|
||||||
&MySQLDialect{},
|
|
||||||
"app_",
|
|
||||||
"user.id = order.user_id AND order.status = 1",
|
|
||||||
[]string{"`app_user`", "`app_order`"},
|
|
||||||
},
|
|
||||||
// PostgreSQL
|
|
||||||
{
|
|
||||||
"PostgreSQL condition",
|
|
||||||
&PostgreSQLDialect{},
|
|
||||||
"app_",
|
|
||||||
"user.id = order.user_id",
|
|
||||||
[]string{"\"app_user\"", "\"app_order\""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
processor := NewIdentifierProcessor(tt.dialect, tt.prefix)
|
|
||||||
result := processor.ProcessConditionString(tt.input)
|
|
||||||
for _, expected := range tt.contains {
|
|
||||||
if !strings.Contains(result, expected) {
|
|
||||||
t.Errorf("ProcessConditionString(%q) = %q, should contain %q", tt.input, result, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestHoTimeDBHelperMethods 测试 HoTimeDB 的辅助方法 T() 和 C()
|
|
||||||
func TestHoTimeDBHelperMethods(t *testing.T) {
|
|
||||||
// 创建 MySQL 数据库实例
|
|
||||||
mysqlDB := &HoTimeDB{
|
|
||||||
Type: "mysql",
|
|
||||||
Prefix: "app_",
|
|
||||||
}
|
|
||||||
mysqlDB.initDialect()
|
|
||||||
|
|
||||||
// 测试 T() 方法
|
|
||||||
t.Run("MySQL T() method", func(t *testing.T) {
|
|
||||||
result := mysqlDB.T("order")
|
|
||||||
expected := "`app_order`"
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("T(\"order\") = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试 C() 方法(两个参数)
|
|
||||||
t.Run("MySQL C() method with two args", func(t *testing.T) {
|
|
||||||
result := mysqlDB.C("order", "name")
|
|
||||||
expected := "`app_order`.`name`"
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("C(\"order\", \"name\") = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试 C() 方法(一个参数,点号格式)
|
|
||||||
t.Run("MySQL C() method with dot notation", func(t *testing.T) {
|
|
||||||
result := mysqlDB.C("order.name")
|
|
||||||
expected := "`app_order`.`name`"
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("C(\"order.name\") = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 创建 PostgreSQL 数据库实例
|
|
||||||
pgDB := &HoTimeDB{
|
|
||||||
Type: "postgres",
|
|
||||||
Prefix: "app_",
|
|
||||||
}
|
|
||||||
pgDB.initDialect()
|
|
||||||
|
|
||||||
// 测试 PostgreSQL 的 T() 方法
|
|
||||||
t.Run("PostgreSQL T() method", func(t *testing.T) {
|
|
||||||
result := pgDB.T("order")
|
|
||||||
expected := "\"app_order\""
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("T(\"order\") = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试 PostgreSQL 的 C() 方法
|
|
||||||
t.Run("PostgreSQL C() method", func(t *testing.T) {
|
|
||||||
result := pgDB.C("order", "name")
|
|
||||||
expected := "\"app_order\".\"name\""
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("C(\"order\", \"name\") = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestWhereWithORCondition 测试 OR 条件处理是否正确添加括号
|
|
||||||
func TestWhereWithORCondition(t *testing.T) {
|
|
||||||
// 创建 MySQL 数据库实例
|
|
||||||
mysqlDB := &HoTimeDB{
|
|
||||||
Type: "mysql",
|
|
||||||
Prefix: "",
|
|
||||||
}
|
|
||||||
mysqlDB.initDialect()
|
|
||||||
|
|
||||||
// 测试 OR 与普通条件组合 (假设 A: 顺序问题)
|
|
||||||
t.Run("OR with normal condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 1 - OR with normal condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 检查 OR 条件是否被括号包裹
|
|
||||||
if !strings.Contains(where, "(") || !strings.Contains(where, ")") {
|
|
||||||
t.Errorf("OR condition should be wrapped with parentheses, got: %s", where)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有 AND 连接
|
|
||||||
if !strings.Contains(where, "AND") {
|
|
||||||
t.Errorf("OR condition and normal condition should be connected with AND, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试纯 OR 条件(无其他普通条件)
|
|
||||||
t.Run("Pure OR condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 2 - Pure OR condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 检查 OR 条件内部应该用 OR 连接
|
|
||||||
if !strings.Contains(where, "OR") {
|
|
||||||
t.Errorf("OR condition should contain OR keyword, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试多个普通条件与 OR 组合 (假设 A)
|
|
||||||
t.Run("OR with multiple normal conditions", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
"status": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 3 - OR with multiple normal conditions:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 应该有括号
|
|
||||||
if !strings.Contains(where, "(") {
|
|
||||||
t.Errorf("OR condition should be wrapped with parentheses, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试嵌套 AND/OR 条件 (假设 B, E)
|
|
||||||
t.Run("Nested AND/OR conditions", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"AND": Map{
|
|
||||||
"phone": "123",
|
|
||||||
"status": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 4 - Nested AND/OR conditions:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试空 OR 条件 (假设 C)
|
|
||||||
t.Run("Empty OR condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 5 - Empty OR condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试 OR 与 LIMIT, ORDER 组合 (假设 D)
|
|
||||||
t.Run("OR with LIMIT and ORDER", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
"ORDER": "id DESC",
|
|
||||||
"LIMIT": 10,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 6 - OR with LIMIT and ORDER:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试同时有 OR 和 AND 关键字 (假设 E)
|
|
||||||
t.Run("Both OR and AND keywords", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"AND": Map{
|
|
||||||
"type": 1,
|
|
||||||
"source": "web",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 7 - Both OR and AND keywords:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试普通条件在 OR 之前(排序后)(假设 A)
|
|
||||||
t.Run("Normal condition before OR alphabetically", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"active": 1, // 'a' 在 'O' 之前
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 8 - Normal condition before OR (alphabetically):")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印测试结果(用于调试)
|
|
||||||
func ExampleIdentifierProcessor() {
|
|
||||||
// MySQL 示例
|
|
||||||
mysqlProcessor := NewIdentifierProcessor(&MySQLDialect{}, "app_")
|
|
||||||
fmt.Println("MySQL:")
|
|
||||||
fmt.Println(" Table:", mysqlProcessor.ProcessTableName("order"))
|
|
||||||
fmt.Println(" Column:", mysqlProcessor.ProcessColumn("order.name"))
|
|
||||||
fmt.Println(" Condition:", mysqlProcessor.ProcessConditionString("user.id = order.user_id"))
|
|
||||||
|
|
||||||
// PostgreSQL 示例
|
|
||||||
pgProcessor := NewIdentifierProcessor(&PostgreSQLDialect{}, "app_")
|
|
||||||
fmt.Println("PostgreSQL:")
|
|
||||||
fmt.Println(" Table:", pgProcessor.ProcessTableName("order"))
|
|
||||||
fmt.Println(" Column:", pgProcessor.ProcessColumn("order.name"))
|
|
||||||
fmt.Println(" Condition:", pgProcessor.ProcessConditionString("user.id = order.user_id"))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// MySQL:
|
|
||||||
// Table: `app_order`
|
|
||||||
// Column: `app_order`.`name`
|
|
||||||
// Condition: `app_user`.`id` = `app_order`.`user_id`
|
|
||||||
// PostgreSQL:
|
|
||||||
// Table: "app_order"
|
|
||||||
// Column: "app_order"."name"
|
|
||||||
// Condition: "app_user"."id" = "app_order"."user_id"
|
|
||||||
}
|
|
||||||
1032
db/hotimedb.go
Normal file
1032
db/hotimedb.go
Normal file
File diff suppressed because it is too large
Load Diff
267
db/identifier.go
267
db/identifier.go
@ -1,267 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IdentifierProcessor 标识符处理器
|
|
||||||
// 用于处理表名、字段名的前缀添加和引号转换
|
|
||||||
type IdentifierProcessor struct {
|
|
||||||
dialect Dialect
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentifierProcessor 创建标识符处理器
|
|
||||||
func NewIdentifierProcessor(dialect Dialect, prefix string) *IdentifierProcessor {
|
|
||||||
return &IdentifierProcessor{
|
|
||||||
dialect: dialect,
|
|
||||||
prefix: prefix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统数据库列表,这些数据库不添加前缀
|
|
||||||
var systemDatabases = map[string]bool{
|
|
||||||
"INFORMATION_SCHEMA": true,
|
|
||||||
"information_schema": true,
|
|
||||||
"mysql": true,
|
|
||||||
"performance_schema": true,
|
|
||||||
"sys": true,
|
|
||||||
"pg_catalog": true,
|
|
||||||
"pg_toast": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessTableName 处理表名(添加前缀+引号)
|
|
||||||
// 输入: "order" 或 "`order`" 或 "\"order\"" 或 "INFORMATION_SCHEMA.TABLES"
|
|
||||||
// 输出: "`app_order`" (MySQL) 或 "\"app_order\"" (PostgreSQL/SQLite)
|
|
||||||
// 对于 database.table 格式,会分别处理,系统数据库不添加前缀
|
|
||||||
func (p *IdentifierProcessor) ProcessTableName(name string) string {
|
|
||||||
// 去除已有的引号
|
|
||||||
name = p.stripQuotes(name)
|
|
||||||
|
|
||||||
// 检查是否包含空格(别名情况,如 "order AS o")
|
|
||||||
if strings.Contains(name, " ") {
|
|
||||||
// 处理别名情况
|
|
||||||
parts := strings.SplitN(name, " ", 2)
|
|
||||||
tableName := p.stripQuotes(parts[0])
|
|
||||||
alias := parts[1]
|
|
||||||
// 递归处理表名部分(可能包含点号)
|
|
||||||
return p.ProcessTableName(tableName) + " " + alias
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否包含点号(database.table 格式)
|
|
||||||
if strings.Contains(name, ".") {
|
|
||||||
parts := p.splitTableColumn(name)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
dbName := p.stripQuotes(parts[0])
|
|
||||||
tableName := p.stripQuotes(parts[1])
|
|
||||||
// 系统数据库不添加前缀
|
|
||||||
if systemDatabases[dbName] {
|
|
||||||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(tableName)
|
|
||||||
}
|
|
||||||
// 非系统数据库,只给表名添加前缀
|
|
||||||
return p.dialect.QuoteIdentifier(dbName) + "." + p.dialect.QuoteIdentifier(p.prefix+tableName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加前缀和引号
|
|
||||||
return p.dialect.QuoteIdentifier(p.prefix + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessTableNameNoPrefix 处理表名(只添加引号,不添加前缀)
|
|
||||||
// 用于已经包含前缀的情况
|
|
||||||
func (p *IdentifierProcessor) ProcessTableNameNoPrefix(name string) string {
|
|
||||||
name = p.stripQuotes(name)
|
|
||||||
if strings.Contains(name, " ") {
|
|
||||||
parts := strings.SplitN(name, " ", 2)
|
|
||||||
tableName := p.stripQuotes(parts[0])
|
|
||||||
alias := parts[1]
|
|
||||||
return p.dialect.QuoteIdentifier(tableName) + " " + alias
|
|
||||||
}
|
|
||||||
return p.dialect.QuoteIdentifier(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessColumn 处理 table.column 格式
|
|
||||||
// 输入: "name" 或 "order.name" 或 "`order`.name" 或 "`order`.`name`"
|
|
||||||
// 输出: "`name`" 或 "`app_order`.`name`" (MySQL)
|
|
||||||
func (p *IdentifierProcessor) ProcessColumn(name string) string {
|
|
||||||
// 检查是否包含点号
|
|
||||||
if !strings.Contains(name, ".") {
|
|
||||||
// 单独的列名,只加引号
|
|
||||||
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 table.column 格式
|
|
||||||
parts := p.splitTableColumn(name)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
tableName := p.stripQuotes(parts[0])
|
|
||||||
columnName := p.stripQuotes(parts[1])
|
|
||||||
// 表名添加前缀
|
|
||||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无法解析,返回原样但转换引号
|
|
||||||
return p.convertQuotes(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessColumnNoPrefix 处理 table.column 格式(不添加前缀)
|
|
||||||
func (p *IdentifierProcessor) ProcessColumnNoPrefix(name string) string {
|
|
||||||
if !strings.Contains(name, ".") {
|
|
||||||
return p.dialect.QuoteIdentifier(p.stripQuotes(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := p.splitTableColumn(name)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
tableName := p.stripQuotes(parts[0])
|
|
||||||
columnName := p.stripQuotes(parts[1])
|
|
||||||
return p.dialect.QuoteIdentifier(tableName) + "." + p.dialect.QuoteIdentifier(columnName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.convertQuotes(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessConditionString 智能解析条件字符串(如 ON 条件)
|
|
||||||
// 输入: "user.id = order.user_id AND order.status = 1"
|
|
||||||
// 输出: "`app_user`.`id` = `app_order`.`user_id` AND `app_order`.`status` = 1" (MySQL)
|
|
||||||
func (p *IdentifierProcessor) ProcessConditionString(condition string) string {
|
|
||||||
if condition == "" {
|
|
||||||
return condition
|
|
||||||
}
|
|
||||||
|
|
||||||
result := condition
|
|
||||||
|
|
||||||
// 首先处理已有完整引号的情况 `table`.`column` 或 "table"."column"
|
|
||||||
// 这些需要先处理,因为它们的格式最明确
|
|
||||||
fullyQuotedPattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]")
|
|
||||||
result = fullyQuotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
|
||||||
parts := fullyQuotedPattern.FindStringSubmatch(match)
|
|
||||||
if len(parts) == 3 {
|
|
||||||
tableName := parts[1]
|
|
||||||
colName := parts[2]
|
|
||||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName)
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
// 然后处理部分引号的情况 `table`.column 或 "table".column
|
|
||||||
// 注意:需要避免匹配已处理的内容(已经是双引号包裹的)
|
|
||||||
quotedTablePattern := regexp.MustCompile("[`\"]([a-zA-Z_][a-zA-Z0-9_]*)[`\"]\\.([a-zA-Z_][a-zA-Z0-9_]*)(?:[^`\"]|$)")
|
|
||||||
result = quotedTablePattern.ReplaceAllStringFunc(result, func(match string) string {
|
|
||||||
parts := quotedTablePattern.FindStringSubmatch(match)
|
|
||||||
if len(parts) >= 3 {
|
|
||||||
tableName := parts[1]
|
|
||||||
colName := parts[2]
|
|
||||||
// 保留末尾字符(如果有)
|
|
||||||
suffix := ""
|
|
||||||
if len(match) > len(parts[0])-1 {
|
|
||||||
lastChar := match[len(match)-1]
|
|
||||||
if lastChar != '`' && lastChar != '"' && !isIdentChar(lastChar) {
|
|
||||||
suffix = string(lastChar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
// 最后处理无引号的情况 table.column
|
|
||||||
// 使用更精确的正则,确保不匹配已处理的内容
|
|
||||||
unquotedPattern := regexp.MustCompile(`([^` + "`" + `"\w]|^)([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)([^` + "`" + `"\w(]|$)`)
|
|
||||||
result = unquotedPattern.ReplaceAllStringFunc(result, func(match string) string {
|
|
||||||
parts := unquotedPattern.FindStringSubmatch(match)
|
|
||||||
if len(parts) >= 5 {
|
|
||||||
prefix := parts[1] // 前面的边界字符
|
|
||||||
tableName := parts[2]
|
|
||||||
colName := parts[3]
|
|
||||||
suffix := parts[4] // 后面的边界字符
|
|
||||||
return prefix + p.dialect.QuoteIdentifier(p.prefix+tableName) + "." + p.dialect.QuoteIdentifier(colName) + suffix
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIdentChar 判断是否是标识符字符
|
|
||||||
func isIdentChar(c byte) bool {
|
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessFieldList 处理字段列表字符串
|
|
||||||
// 输入: "order.id, user.name AS uname, COUNT(*)"
|
|
||||||
// 输出: "`app_order`.`id`, `app_user`.`name` AS uname, COUNT(*)" (MySQL)
|
|
||||||
func (p *IdentifierProcessor) ProcessFieldList(fields string) string {
|
|
||||||
if fields == "" || fields == "*" {
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用与 ProcessConditionString 相同的逻辑
|
|
||||||
return p.ProcessConditionString(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripQuotes 去除标识符两端的引号(反引号或双引号)
|
|
||||||
func (p *IdentifierProcessor) stripQuotes(name string) string {
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
// 去除反引号
|
|
||||||
if strings.HasPrefix(name, "`") && strings.HasSuffix(name, "`") {
|
|
||||||
return name[1 : len(name)-1]
|
|
||||||
}
|
|
||||||
// 去除双引号
|
|
||||||
if strings.HasPrefix(name, "\"") && strings.HasSuffix(name, "\"") {
|
|
||||||
return name[1 : len(name)-1]
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// splitTableColumn 分割 table.column 格式
|
|
||||||
// 支持: table.column, `table`.column, `table`.`column`, "table".column 等
|
|
||||||
func (p *IdentifierProcessor) splitTableColumn(name string) []string {
|
|
||||||
// 先尝试按点号分割
|
|
||||||
dotIndex := -1
|
|
||||||
|
|
||||||
// 查找不在引号内的点号
|
|
||||||
inQuote := false
|
|
||||||
quoteChar := byte(0)
|
|
||||||
for i := 0; i < len(name); i++ {
|
|
||||||
c := name[i]
|
|
||||||
if c == '`' || c == '"' {
|
|
||||||
if !inQuote {
|
|
||||||
inQuote = true
|
|
||||||
quoteChar = c
|
|
||||||
} else if c == quoteChar {
|
|
||||||
inQuote = false
|
|
||||||
}
|
|
||||||
} else if c == '.' && !inQuote {
|
|
||||||
dotIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dotIndex == -1 {
|
|
||||||
return []string{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{name[:dotIndex], name[dotIndex+1:]}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertQuotes 将已有的引号转换为当前方言的引号格式
|
|
||||||
func (p *IdentifierProcessor) convertQuotes(name string) string {
|
|
||||||
quoteChar := p.dialect.QuoteChar()
|
|
||||||
// 替换反引号
|
|
||||||
name = strings.ReplaceAll(name, "`", quoteChar)
|
|
||||||
// 如果目标是反引号,需要替换双引号
|
|
||||||
if quoteChar == "`" {
|
|
||||||
name = strings.ReplaceAll(name, "\"", quoteChar)
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDialect 获取方言
|
|
||||||
func (p *IdentifierProcessor) GetDialect() Dialect {
|
|
||||||
return p.dialect
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrefix 获取前缀
|
|
||||||
func (p *IdentifierProcessor) GetPrefix() string {
|
|
||||||
return p.prefix
|
|
||||||
}
|
|
||||||
391
db/query.go
391
db/query.go
@ -1,391 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
// 预处理数组占位符 ?[]
|
|
||||||
query, args = that.expandArrayPlaceholder(query, args)
|
|
||||||
|
|
||||||
// 保存调试信息(加锁保护)
|
|
||||||
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) {
|
|
||||||
// 预处理数组占位符 ?[]
|
|
||||||
query, args = that.expandArrayPlaceholder(query, args)
|
|
||||||
|
|
||||||
// 保存调试信息(加锁保护)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// expandArrayPlaceholder 展开 IN (?) / NOT IN (?) 中的数组参数
|
|
||||||
// 自动识别 IN/NOT IN (?) 模式,当参数是数组时展开为多个 ?
|
|
||||||
//
|
|
||||||
// 示例:
|
|
||||||
//
|
|
||||||
// db.Query("SELECT * FROM user WHERE id IN (?)", []int{1, 2, 3})
|
|
||||||
// // 展开为: SELECT * FROM user WHERE id IN (?, ?, ?) 参数: [1, 2, 3]
|
|
||||||
//
|
|
||||||
// db.Query("SELECT * FROM user WHERE id IN (?)", []int{})
|
|
||||||
// // 展开为: SELECT * FROM user WHERE 1=0 参数: [] (空集合的IN永假)
|
|
||||||
//
|
|
||||||
// db.Query("SELECT * FROM user WHERE id NOT IN (?)", []int{})
|
|
||||||
// // 展开为: SELECT * FROM user WHERE 1=1 参数: [] (空集合的NOT IN永真)
|
|
||||||
//
|
|
||||||
// db.Query("SELECT * FROM user WHERE id = ?", 1)
|
|
||||||
// // 保持不变: SELECT * FROM user WHERE id = ? 参数: [1]
|
|
||||||
func (that *HoTimeDB) expandArrayPlaceholder(query string, args []interface{}) (string, []interface{}) {
|
|
||||||
if len(args) == 0 || !strings.Contains(query, "?") {
|
|
||||||
return query, args
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有数组参数
|
|
||||||
hasArray := false
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
argType := reflect.ValueOf(arg).Type().String()
|
|
||||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
|
||||||
hasArray = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasArray {
|
|
||||||
return query, args
|
|
||||||
}
|
|
||||||
|
|
||||||
newArgs := make([]interface{}, 0, len(args))
|
|
||||||
result := strings.Builder{}
|
|
||||||
argIndex := 0
|
|
||||||
|
|
||||||
for i := 0; i < len(query); i++ {
|
|
||||||
if query[i] == '?' && argIndex < len(args) {
|
|
||||||
arg := args[argIndex]
|
|
||||||
argIndex++
|
|
||||||
|
|
||||||
if arg == nil {
|
|
||||||
result.WriteByte('?')
|
|
||||||
newArgs = append(newArgs, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
argType := reflect.ValueOf(arg).Type().String()
|
|
||||||
if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") {
|
|
||||||
// 是数组参数,检查是否在 IN (...) 或 NOT IN (...) 中
|
|
||||||
prevPart := result.String()
|
|
||||||
prevUpper := strings.ToUpper(prevPart)
|
|
||||||
|
|
||||||
// 查找最近的 NOT IN ( 模式
|
|
||||||
notInIndex := strings.LastIndex(prevUpper, " NOT IN (")
|
|
||||||
notInIndex2 := strings.LastIndex(prevUpper, " NOT IN(")
|
|
||||||
if notInIndex2 > notInIndex {
|
|
||||||
notInIndex = notInIndex2
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找最近的 IN ( 模式(但要排除 NOT IN 的情况)
|
|
||||||
inIndex := strings.LastIndex(prevUpper, " IN (")
|
|
||||||
inIndex2 := strings.LastIndex(prevUpper, " IN(")
|
|
||||||
if inIndex2 > inIndex {
|
|
||||||
inIndex = inIndex2
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是 NOT IN 还是 IN
|
|
||||||
// 注意:" NOT IN (" 包含 " IN (",所以如果找到的 IN 位置在 NOT IN 范围内,应该优先判断为 NOT IN
|
|
||||||
isNotIn := false
|
|
||||||
matchIndex := -1
|
|
||||||
if notInIndex != -1 {
|
|
||||||
// 检查 inIndex 是否在 notInIndex 范围内(即 NOT IN 的 IN 部分)
|
|
||||||
// NOT IN ( 的 IN ( 部分从 notInIndex + 4 开始
|
|
||||||
if inIndex != -1 && inIndex >= notInIndex && inIndex <= notInIndex+5 {
|
|
||||||
// inIndex 是 NOT IN 的一部分,使用 NOT IN
|
|
||||||
isNotIn = true
|
|
||||||
matchIndex = notInIndex
|
|
||||||
} else if inIndex == -1 || notInIndex > inIndex {
|
|
||||||
// 没有独立的 IN,或 NOT IN 在 IN 之后
|
|
||||||
isNotIn = true
|
|
||||||
matchIndex = notInIndex
|
|
||||||
} else {
|
|
||||||
// 有独立的 IN 且在 NOT IN 之后
|
|
||||||
matchIndex = inIndex
|
|
||||||
}
|
|
||||||
} else if inIndex != -1 {
|
|
||||||
matchIndex = inIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 IN ( 后面是否只有空格(即当前 ? 紧跟在 IN ( 后面)
|
|
||||||
isInPattern := false
|
|
||||||
if matchIndex != -1 {
|
|
||||||
afterIn := prevPart[matchIndex:]
|
|
||||||
// 找到 ( 的位置
|
|
||||||
parenIdx := strings.Index(afterIn, "(")
|
|
||||||
if parenIdx != -1 {
|
|
||||||
afterParen := strings.TrimSpace(afterIn[parenIdx+1:])
|
|
||||||
if afterParen == "" {
|
|
||||||
isInPattern = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInPattern {
|
|
||||||
// 在 IN (...) 或 NOT IN (...) 模式中
|
|
||||||
argList := ObjToSlice(arg)
|
|
||||||
if len(argList) == 0 {
|
|
||||||
// 空数组处理:需要找到字段名的开始位置
|
|
||||||
// 往前找最近的 AND/OR/WHERE/(,以确定条件的开始位置
|
|
||||||
truncateIndex := matchIndex
|
|
||||||
searchPart := prevUpper[:matchIndex]
|
|
||||||
|
|
||||||
// 找最近的分隔符位置
|
|
||||||
andIdx := strings.LastIndex(searchPart, " AND ")
|
|
||||||
orIdx := strings.LastIndex(searchPart, " OR ")
|
|
||||||
whereIdx := strings.LastIndex(searchPart, " WHERE ")
|
|
||||||
parenIdx := strings.LastIndex(searchPart, "(")
|
|
||||||
|
|
||||||
// 取最靠后的分隔符
|
|
||||||
sepIndex := -1
|
|
||||||
sepLen := 0
|
|
||||||
if andIdx > sepIndex {
|
|
||||||
sepIndex = andIdx
|
|
||||||
sepLen = 5 // " AND "
|
|
||||||
}
|
|
||||||
if orIdx > sepIndex {
|
|
||||||
sepIndex = orIdx
|
|
||||||
sepLen = 4 // " OR "
|
|
||||||
}
|
|
||||||
if whereIdx > sepIndex {
|
|
||||||
sepIndex = whereIdx
|
|
||||||
sepLen = 7 // " WHERE "
|
|
||||||
}
|
|
||||||
if parenIdx > sepIndex {
|
|
||||||
sepIndex = parenIdx
|
|
||||||
sepLen = 1 // "("
|
|
||||||
}
|
|
||||||
|
|
||||||
if sepIndex != -1 {
|
|
||||||
truncateIndex = sepIndex + sepLen
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Reset()
|
|
||||||
result.WriteString(prevPart[:truncateIndex])
|
|
||||||
if isNotIn {
|
|
||||||
// NOT IN 空集合 = 永真
|
|
||||||
result.WriteString(" 1=1 ")
|
|
||||||
} else {
|
|
||||||
// IN 空集合 = 永假
|
|
||||||
result.WriteString(" 1=0 ")
|
|
||||||
}
|
|
||||||
// 跳过后面的 )
|
|
||||||
for j := i + 1; j < len(query); j++ {
|
|
||||||
if query[j] == ')' {
|
|
||||||
i = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if len(argList) == 1 {
|
|
||||||
// 单元素数组
|
|
||||||
result.WriteByte('?')
|
|
||||||
newArgs = append(newArgs, argList[0])
|
|
||||||
} else {
|
|
||||||
// 多元素数组,展开为多个 ?
|
|
||||||
for j := 0; j < len(argList); j++ {
|
|
||||||
if j > 0 {
|
|
||||||
result.WriteString(", ")
|
|
||||||
}
|
|
||||||
result.WriteByte('?')
|
|
||||||
newArgs = append(newArgs, argList[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 不在 IN 模式中,保持原有行为(数组会被 processArgs 转为逗号字符串)
|
|
||||||
result.WriteByte('?')
|
|
||||||
newArgs = append(newArgs, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 非数组参数
|
|
||||||
result.WriteByte('?')
|
|
||||||
newArgs = append(newArgs, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.WriteByte(query[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.String(), newArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
555
db/where.go
555
db/where.go
@ -1,555 +0,0 @@
|
|||||||
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
|
|
||||||
condCount := 0
|
|
||||||
|
|
||||||
for _, k := range testQu {
|
|
||||||
v := data[k]
|
|
||||||
|
|
||||||
// 检查是否是 AND/OR 条件关键字
|
|
||||||
if isConditionKey(k) {
|
|
||||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
|
||||||
if tw != "" && strings.TrimSpace(tw) != "" {
|
|
||||||
// 与前面的条件用 AND 连接
|
|
||||||
if condCount > 0 {
|
|
||||||
where += " AND "
|
|
||||||
}
|
|
||||||
// 用括号包裹 OR/AND 组条件
|
|
||||||
where += "(" + strings.TrimSpace(tw) + ")"
|
|
||||||
condCount++
|
|
||||||
res = append(res, ts...)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是特殊关键字(GROUP, ORDER, LIMIT 等)
|
|
||||||
if isVcondKey(k) {
|
|
||||||
continue // 特殊关键字在后面单独处理
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理普通条件字段
|
|
||||||
// 空切片的 IN 条件应该生成永假条件(1=0),而不是跳过
|
|
||||||
if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 {
|
|
||||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
|
||||||
if !strings.HasSuffix(k, "[!]") {
|
|
||||||
// IN 空数组 -> 生成永假条件
|
|
||||||
if condCount > 0 {
|
|
||||||
where += " AND "
|
|
||||||
}
|
|
||||||
where += "1=0 "
|
|
||||||
condCount++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 {
|
|
||||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
|
||||||
if !strings.HasSuffix(k, "[!]") {
|
|
||||||
// IN 空数组 -> 生成永假条件
|
|
||||||
if condCount > 0 {
|
|
||||||
where += " AND "
|
|
||||||
}
|
|
||||||
where += "1=0 "
|
|
||||||
condCount++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tv, vv := that.varCond(k, v)
|
|
||||||
if tv != "" {
|
|
||||||
// 自动添加 AND 连接符
|
|
||||||
if condCount > 0 {
|
|
||||||
where += " AND "
|
|
||||||
}
|
|
||||||
where += tv
|
|
||||||
condCount++
|
|
||||||
res = append(res, vv...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加 WHERE 关键字
|
|
||||||
// 先去除首尾空格,检查是否有实际条件内容
|
|
||||||
trimmedWhere := strings.TrimSpace(where)
|
|
||||||
if len(trimmedWhere) != 0 {
|
|
||||||
hasWhere := true
|
|
||||||
for _, v := range vcond {
|
|
||||||
if strings.Index(trimmedWhere, v) == 0 {
|
|
||||||
hasWhere = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasWhere {
|
|
||||||
where = " WHERE " + trimmedWhere + " "
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 没有实际条件内容,重置 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)
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
|
|
||||||
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)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + ">? "
|
|
||||||
res = append(res, v)
|
|
||||||
case "[<]":
|
|
||||||
k = strings.Replace(k, "[<]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + "<? "
|
|
||||||
res = append(res, v)
|
|
||||||
case "[!]":
|
|
||||||
k = strings.Replace(k, "[!]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where, res = that.notIn(k, v, where, res)
|
|
||||||
case "[#]":
|
|
||||||
k = strings.Replace(k, "[#]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += " " + k + "=" + ObjToStr(v) + " "
|
|
||||||
case "[##]": // 直接添加value到sql,需要考虑防注入
|
|
||||||
where += " " + ObjToStr(v)
|
|
||||||
case "[#!]":
|
|
||||||
k = strings.Replace(k, "[#!]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
|
||||||
case "[!#]":
|
|
||||||
k = strings.Replace(k, "[!#]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += " " + k + "!=" + ObjToStr(v) + " "
|
|
||||||
case "[~]":
|
|
||||||
k = strings.Replace(k, "[~]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + " LIKE ? "
|
|
||||||
v = "%" + ObjToStr(v) + "%"
|
|
||||||
res = append(res, v)
|
|
||||||
case "[!~]": // 左边任意
|
|
||||||
k = strings.Replace(k, "[!~]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + " LIKE ? "
|
|
||||||
v = "%" + ObjToStr(v) + ""
|
|
||||||
res = append(res, v)
|
|
||||||
case "[~!]": // 右边任意
|
|
||||||
k = strings.Replace(k, "[~!]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + " LIKE ? "
|
|
||||||
v = ObjToStr(v) + "%"
|
|
||||||
res = append(res, v)
|
|
||||||
case "[~~]": // 手动任意
|
|
||||||
k = strings.Replace(k, "[~~]", "", -1)
|
|
||||||
k = processor.ProcessColumn(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)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + ">=? "
|
|
||||||
res = append(res, v)
|
|
||||||
case "[<=]":
|
|
||||||
k = strings.Replace(k, "[<=]", "", -1)
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
where += k + "<=? "
|
|
||||||
res = append(res, v)
|
|
||||||
case "[><]":
|
|
||||||
k = strings.Replace(k, "[><]", "", -1)
|
|
||||||
k = processor.ProcessColumn(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)
|
|
||||||
k = processor.ProcessColumn(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{}) {
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
k = processor.ProcessColumn(k) + " "
|
|
||||||
|
|
||||||
if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") {
|
|
||||||
vs := ObjToSlice(v)
|
|
||||||
if len(vs) == 0 {
|
|
||||||
// IN 空数组 -> 生成永假条件
|
|
||||||
where += "1=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{}) {
|
|
||||||
processor := that.GetProcessor()
|
|
||||||
k = processor.ProcessColumn(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 {
|
|
||||||
// IN 空数组 -> 生成永假条件
|
|
||||||
where += "1=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
|
|
||||||
}
|
|
||||||
@ -1,468 +0,0 @@
|
|||||||
# HoTime 代码生成器使用说明
|
|
||||||
|
|
||||||
`code` 包提供了 HoTime 框架的自动代码生成功能,能够根据数据库表结构自动生成 CRUD 接口代码和配置文件。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [功能概述](#功能概述)
|
|
||||||
- [配置说明](#配置说明)
|
|
||||||
- [使用方法](#使用方法)
|
|
||||||
- [生成规则](#生成规则)
|
|
||||||
- [自定义规则](#自定义规则)
|
|
||||||
- [生成的代码结构](#生成的代码结构)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
|
|
||||||
代码生成器可以:
|
|
||||||
|
|
||||||
1. **自动读取数据库表结构** - 支持 MySQL 和 SQLite
|
|
||||||
2. **生成 CRUD 接口** - 增删改查、搜索、分页
|
|
||||||
3. **生成配置文件** - 表字段配置、菜单配置、权限配置
|
|
||||||
4. **智能字段识别** - 根据字段名自动识别类型和权限
|
|
||||||
5. **支持表关联** - 自动识别外键关系
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置说明
|
|
||||||
|
|
||||||
在 `config.json` 中配置代码生成:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"codeConfig": [
|
|
||||||
{
|
|
||||||
"table": "admin",
|
|
||||||
"config": "config/admin.json",
|
|
||||||
"configDB": "config/adminDB.json",
|
|
||||||
"rule": "config/rule.json",
|
|
||||||
"name": "",
|
|
||||||
"mode": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 配置项说明
|
|
||||||
|
|
||||||
| 配置项 | 必须 | 说明 |
|
|
||||||
|--------|------|------|
|
|
||||||
| `table` | ✅ | 用户表名,用于权限控制的基准表 |
|
|
||||||
| `config` | ✅ | 接口描述配置文件路径 |
|
|
||||||
| `configDB` | ❌ | 数据库结构配置输出路径,有则每次自动生成 |
|
|
||||||
| `rule` | ❌ | 字段规则配置文件,无则使用默认规则 |
|
|
||||||
| `name` | ❌ | 生成代码的包名和目录名,空则使用内嵌模式 |
|
|
||||||
| `mode` | ❌ | 0=内嵌代码模式,1=生成代码模式 |
|
|
||||||
|
|
||||||
### 运行模式
|
|
||||||
|
|
||||||
- **mode=0(内嵌模式)**:不生成独立代码文件,使用框架内置的通用控制器
|
|
||||||
- **mode=1(生成模式)**:为每张表生成独立的 Go 控制器文件
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 1. 基础配置
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mode": 2,
|
|
||||||
"codeConfig": [
|
|
||||||
{
|
|
||||||
"table": "admin",
|
|
||||||
"config": "config/admin.json",
|
|
||||||
"rule": "config/rule.json",
|
|
||||||
"mode": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 启动应用
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "code.hoteas.com/golang/hotime"
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := Init("config/config.json")
|
|
||||||
|
|
||||||
// 代码生成器在 Init 时自动执行
|
|
||||||
// 会读取数据库结构并生成配置
|
|
||||||
|
|
||||||
app.Run(Router{
|
|
||||||
// 路由配置
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 开发模式
|
|
||||||
|
|
||||||
在 `config.json` 中设置 `"mode": 2`(开发模式)时:
|
|
||||||
|
|
||||||
- 自动读取数据库表结构
|
|
||||||
- 自动生成/更新配置文件
|
|
||||||
- 自动生成代码(如果 codeConfig.mode=1)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 生成规则
|
|
||||||
|
|
||||||
### 默认字段规则
|
|
||||||
|
|
||||||
代码生成器内置了一套默认的字段识别规则:
|
|
||||||
|
|
||||||
| 字段名 | 列表显示 | 新增 | 编辑 | 详情 | 类型 |
|
|
||||||
|--------|----------|------|------|------|------|
|
|
||||||
| `id` | ✅ | ❌ | ❌ | ✅ | number |
|
|
||||||
| `name` | ✅ | ✅ | ✅ | ✅ | text |
|
|
||||||
| `status` | ✅ | ✅ | ✅ | ✅ | select |
|
|
||||||
| `create_time` | ❌ | ❌ | ❌ | ✅ | time |
|
|
||||||
| `modify_time` | ✅ | ❌ | ❌ | ✅ | time |
|
|
||||||
| `password` | ❌ | ✅ | ✅ | ❌ | password |
|
|
||||||
| `image/img/avatar` | ❌ | ✅ | ✅ | ✅ | image |
|
|
||||||
| `file` | ❌ | ✅ | ✅ | ✅ | file |
|
|
||||||
| `content/info` | ❌ | ✅ | ✅ | ✅ | textArea |
|
|
||||||
| `parent_id` | ✅ | ✅ | ✅ | ✅ | number |
|
|
||||||
| `parent_ids/index` | ❌ | ❌ | ❌ | ❌ | index |
|
|
||||||
| `delete` | ❌ | ❌ | ❌ | ❌ | - |
|
|
||||||
|
|
||||||
### 数据类型映射
|
|
||||||
|
|
||||||
数据库字段类型自动映射:
|
|
||||||
|
|
||||||
| 数据库类型 | 生成类型 |
|
|
||||||
|------------|----------|
|
|
||||||
| `int`, `integer`, `float`, `double`, `decimal` | number |
|
|
||||||
| `char`, `varchar`, `text`, `blob` | text |
|
|
||||||
| `date`, `datetime`, `time`, `timestamp`, `year` | time |
|
|
||||||
|
|
||||||
### 字段备注解析
|
|
||||||
|
|
||||||
支持从数据库字段备注中提取信息:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 字段备注格式: 标签名:选项1-名称1,选项2-名称2 {提示信息}
|
|
||||||
-- 例如:
|
|
||||||
status TINYINT COMMENT '状态:0-禁用,1-启用 {用户账号状态}'
|
|
||||||
```
|
|
||||||
|
|
||||||
生成的配置:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "status",
|
|
||||||
"label": "状态",
|
|
||||||
"type": "select",
|
|
||||||
"ps": "用户账号状态",
|
|
||||||
"options": [
|
|
||||||
{"name": "禁用", "value": "0"},
|
|
||||||
{"name": "启用", "value": "1"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 自定义规则
|
|
||||||
|
|
||||||
### rule.json 配置
|
|
||||||
|
|
||||||
创建 `config/rule.json` 自定义字段规则:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"list": true,
|
|
||||||
"add": false,
|
|
||||||
"edit": false,
|
|
||||||
"info": true,
|
|
||||||
"must": false,
|
|
||||||
"strict": true,
|
|
||||||
"type": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status",
|
|
||||||
"list": true,
|
|
||||||
"add": true,
|
|
||||||
"edit": true,
|
|
||||||
"info": true,
|
|
||||||
"must": false,
|
|
||||||
"strict": false,
|
|
||||||
"type": "select"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user.special_field",
|
|
||||||
"list": true,
|
|
||||||
"add": true,
|
|
||||||
"edit": true,
|
|
||||||
"info": true,
|
|
||||||
"type": "text",
|
|
||||||
"strict": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 规则字段说明
|
|
||||||
|
|
||||||
| 字段 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `name` | 字段名,支持 `表名.字段名` 格式精确匹配 |
|
|
||||||
| `list` | 是否在列表中显示 |
|
|
||||||
| `add` | 是否在新增表单中显示 |
|
|
||||||
| `edit` | 是否在编辑表单中显示 |
|
|
||||||
| `info` | 是否在详情中显示 |
|
|
||||||
| `must` | 是否必填 |
|
|
||||||
| `strict` | 是否严格匹配字段名(false 则模糊匹配) |
|
|
||||||
| `type` | 字段类型(覆盖自动识别) |
|
|
||||||
|
|
||||||
### 字段类型
|
|
||||||
|
|
||||||
| 类型 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `text` | 普通文本输入 |
|
|
||||||
| `textArea` | 多行文本 |
|
|
||||||
| `number` | 数字输入 |
|
|
||||||
| `select` | 下拉选择 |
|
|
||||||
| `time` | 时间选择器 |
|
|
||||||
| `unixTime` | Unix 时间戳 |
|
|
||||||
| `image` | 图片上传 |
|
|
||||||
| `file` | 文件上传 |
|
|
||||||
| `password` | 密码输入 |
|
|
||||||
| `money` | 金额(带格式化) |
|
|
||||||
| `index` | 索引字段(不显示) |
|
|
||||||
| `tree` | 树形选择 |
|
|
||||||
| `form` | 表单配置 |
|
|
||||||
| `auth` | 权限配置 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 生成的代码结构
|
|
||||||
|
|
||||||
### 内嵌模式 (mode=0)
|
|
||||||
|
|
||||||
不生成代码文件,使用框架内置控制器,只生成配置文件:
|
|
||||||
|
|
||||||
```
|
|
||||||
config/
|
|
||||||
├── admin.json # 接口配置
|
|
||||||
├── adminDB.json # 数据库结构配置(可选)
|
|
||||||
└── rule.json # 字段规则
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生成模式 (mode=1)
|
|
||||||
|
|
||||||
生成独立的控制器代码:
|
|
||||||
|
|
||||||
```
|
|
||||||
admin/ # 生成的包目录
|
|
||||||
├── init.go # 包初始化和路由注册
|
|
||||||
├── user.go # user 表控制器
|
|
||||||
├── role.go # role 表控制器
|
|
||||||
└── ... # 其他表控制器
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生成的控制器结构
|
|
||||||
|
|
||||||
```go
|
|
||||||
package admin
|
|
||||||
|
|
||||||
var userCtr = Ctr{
|
|
||||||
"info": func(that *Context) {
|
|
||||||
// 查询单条记录
|
|
||||||
},
|
|
||||||
"add": func(that *Context) {
|
|
||||||
// 新增记录
|
|
||||||
},
|
|
||||||
"update": func(that *Context) {
|
|
||||||
// 更新记录
|
|
||||||
},
|
|
||||||
"remove": func(that *Context) {
|
|
||||||
// 删除记录
|
|
||||||
},
|
|
||||||
"search": func(that *Context) {
|
|
||||||
// 搜索列表(分页)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置文件结构
|
|
||||||
|
|
||||||
### admin.json 示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"label": "管理平台",
|
|
||||||
"menus": [
|
|
||||||
{
|
|
||||||
"label": "系统管理",
|
|
||||||
"name": "sys",
|
|
||||||
"icon": "Setting",
|
|
||||||
"menus": [
|
|
||||||
{
|
|
||||||
"label": "用户管理",
|
|
||||||
"table": "user",
|
|
||||||
"auth": ["show", "add", "delete", "edit", "info", "download"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "角色管理",
|
|
||||||
"table": "role",
|
|
||||||
"auth": ["show", "add", "delete", "edit", "info"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tables": {
|
|
||||||
"user": {
|
|
||||||
"label": "用户",
|
|
||||||
"table": "user",
|
|
||||||
"auth": ["show", "add", "delete", "edit", "info", "download"],
|
|
||||||
"columns": [
|
|
||||||
{"name": "id", "type": "number", "label": "ID"},
|
|
||||||
{"name": "name", "type": "text", "label": "用户名"},
|
|
||||||
{"name": "status", "type": "select", "label": "状态",
|
|
||||||
"options": [{"name": "禁用", "value": "0"}, {"name": "启用", "value": "1"}]}
|
|
||||||
],
|
|
||||||
"search": [
|
|
||||||
{"type": "search", "name": "keyword", "label": "请输入关键词"},
|
|
||||||
{"type": "search", "name": "daterange", "label": "时间段"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 外键关联
|
|
||||||
|
|
||||||
### 自动识别
|
|
||||||
|
|
||||||
代码生成器会自动识别 `_id` 结尾的字段作为外键:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- user 表
|
|
||||||
CREATE TABLE user (
|
|
||||||
id INT PRIMARY KEY,
|
|
||||||
name VARCHAR(50),
|
|
||||||
role_id INT, -- 自动关联 role 表
|
|
||||||
org_id INT -- 自动关联 org 表
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
生成的配置会包含 `link` 和 `value` 字段:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "role_id",
|
|
||||||
"type": "number",
|
|
||||||
"label": "角色",
|
|
||||||
"link": "role",
|
|
||||||
"value": "name"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 树形结构
|
|
||||||
|
|
||||||
`parent_id` 字段会被识别为树形结构的父级关联:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "parent_id",
|
|
||||||
"type": "number",
|
|
||||||
"label": "上级",
|
|
||||||
"link": "org",
|
|
||||||
"value": "name"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 权限控制
|
|
||||||
|
|
||||||
### 数据权限
|
|
||||||
|
|
||||||
配置 `flow` 实现数据权限控制:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"flow": {
|
|
||||||
"order": {
|
|
||||||
"table": "order",
|
|
||||||
"stop": false,
|
|
||||||
"sql": {
|
|
||||||
"user_id": "id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `stop`: 是否禁止修改该表
|
|
||||||
- `sql`: 数据过滤条件,`user_id = 当前用户.id`
|
|
||||||
|
|
||||||
### 操作权限
|
|
||||||
|
|
||||||
每张表可配置的权限:
|
|
||||||
|
|
||||||
| 权限 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `show` | 查看列表 |
|
|
||||||
| `add` | 新增 |
|
|
||||||
| `edit` | 编辑 |
|
|
||||||
| `delete` | 删除 |
|
|
||||||
| `info` | 查看详情 |
|
|
||||||
| `download` | 下载导出 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 开发流程
|
|
||||||
|
|
||||||
1. 设置 `config.json` 中 `mode: 2`(开发模式)
|
|
||||||
2. 设计数据库表结构,添加字段备注
|
|
||||||
3. 启动应用,自动生成配置
|
|
||||||
4. 检查生成的配置文件,按需调整
|
|
||||||
5. 生产环境改为 `mode: 0`
|
|
||||||
|
|
||||||
### 2. 字段命名规范
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 推荐的命名方式
|
|
||||||
id -- 主键
|
|
||||||
name -- 名称
|
|
||||||
status -- 状态(自动识别为 select)
|
|
||||||
create_time -- 创建时间
|
|
||||||
modify_time -- 修改时间
|
|
||||||
xxx_id -- 外键关联
|
|
||||||
parent_id -- 树形结构父级
|
|
||||||
avatar -- 头像(自动识别为 image)
|
|
||||||
content -- 内容(自动识别为 textArea)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 自定义扩展
|
|
||||||
|
|
||||||
如果默认规则不满足需求,可以:
|
|
||||||
|
|
||||||
1. 修改 `rule.json` 添加自定义规则
|
|
||||||
2. 使用 `mode=1` 生成代码后手动修改
|
|
||||||
3. 在生成的配置文件中直接调整字段属性
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关文档
|
|
||||||
|
|
||||||
- [快速上手指南](QUICKSTART.md)
|
|
||||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md)
|
|
||||||
- [Common 工具类使用说明](Common_工具类使用说明.md)
|
|
||||||
@ -1,484 +0,0 @@
|
|||||||
# HoTime Common 工具类使用说明
|
|
||||||
|
|
||||||
`common` 包提供了 HoTime 框架的核心数据类型和工具函数,包括 `Map`、`Slice`、`Obj` 类型及丰富的类型转换函数。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [核心数据类型](#核心数据类型)
|
|
||||||
- [Map 类型](#map-类型)
|
|
||||||
- [Slice 类型](#slice-类型)
|
|
||||||
- [Obj 类型](#obj-类型)
|
|
||||||
- [类型转换函数](#类型转换函数)
|
|
||||||
- [工具函数](#工具函数)
|
|
||||||
- [错误处理](#错误处理)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心数据类型
|
|
||||||
|
|
||||||
### Map 类型
|
|
||||||
|
|
||||||
`Map` 是 `map[string]interface{}` 的别名,提供了丰富的链式调用方法。
|
|
||||||
|
|
||||||
```go
|
|
||||||
import . "code.hoteas.com/golang/hotime/common"
|
|
||||||
|
|
||||||
// 创建 Map
|
|
||||||
data := Map{
|
|
||||||
"name": "张三",
|
|
||||||
"age": 25,
|
|
||||||
"score": 98.5,
|
|
||||||
"active": true,
|
|
||||||
"tags": Slice{"Go", "Web"},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取值方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 获取字符串
|
|
||||||
name := data.GetString("name") // "张三"
|
|
||||||
|
|
||||||
// 获取整数
|
|
||||||
age := data.GetInt("age") // 25
|
|
||||||
age64 := data.GetInt64("age") // int64(25)
|
|
||||||
|
|
||||||
// 获取浮点数
|
|
||||||
score := data.GetFloat64("score") // 98.5
|
|
||||||
|
|
||||||
// 获取布尔值
|
|
||||||
active := data.GetBool("active") // true
|
|
||||||
|
|
||||||
// 获取嵌套 Map
|
|
||||||
info := data.GetMap("info") // 返回 Map 类型
|
|
||||||
|
|
||||||
// 获取 Slice
|
|
||||||
tags := data.GetSlice("tags") // 返回 Slice 类型
|
|
||||||
|
|
||||||
// 获取时间
|
|
||||||
createTime := data.GetTime("create_time") // 返回 *time.Time
|
|
||||||
|
|
||||||
// 获取原始值
|
|
||||||
raw := data.Get("name") // interface{}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 向上取整方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 向上取整获取整数
|
|
||||||
ceilInt := data.GetCeilInt("score") // 99
|
|
||||||
ceilInt64 := data.GetCeilInt64("score") // int64(99)
|
|
||||||
ceilFloat := data.GetCeilFloat64("score") // 99.0
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 操作方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 添加/修改值
|
|
||||||
data.Put("email", "test@example.com")
|
|
||||||
|
|
||||||
// 删除值
|
|
||||||
data.Delete("email")
|
|
||||||
|
|
||||||
// 转换为 JSON 字符串
|
|
||||||
jsonStr := data.ToJsonString()
|
|
||||||
|
|
||||||
// 从 JSON 字符串解析
|
|
||||||
data.JsonToMap(`{"key": "value"}`)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 有序遍历
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 按 key 字母顺序遍历
|
|
||||||
data.RangeSort(func(k string, v interface{}) bool {
|
|
||||||
fmt.Printf("%s: %v\n", k, v)
|
|
||||||
return false // 返回 true 则终止遍历
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 转换为结构体
|
|
||||||
|
|
||||||
```go
|
|
||||||
type User struct {
|
|
||||||
Name string
|
|
||||||
Age int64
|
|
||||||
Score float64
|
|
||||||
}
|
|
||||||
|
|
||||||
var user User
|
|
||||||
data.ToStruct(&user) // 传入指针,字段名首字母大写匹配
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Slice 类型
|
|
||||||
|
|
||||||
`Slice` 是 `[]interface{}` 的别名,提供类似 Map 的链式调用方法。
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 创建 Slice
|
|
||||||
list := Slice{
|
|
||||||
Map{"id": 1, "name": "Alice"},
|
|
||||||
Map{"id": 2, "name": "Bob"},
|
|
||||||
"text",
|
|
||||||
123,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取值方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 按索引获取值(类型转换)
|
|
||||||
str := list.GetString(2) // "text"
|
|
||||||
num := list.GetInt(3) // 123
|
|
||||||
num64 := list.GetInt64(3) // int64(123)
|
|
||||||
f := list.GetFloat64(3) // 123.0
|
|
||||||
b := list.GetBool(3) // true (非0为true)
|
|
||||||
|
|
||||||
// 获取嵌套类型
|
|
||||||
item := list.GetMap(0) // Map{"id": 1, "name": "Alice"}
|
|
||||||
subList := list.GetSlice(0) // 尝试转换为 Slice
|
|
||||||
|
|
||||||
// 获取原始值
|
|
||||||
raw := list.Get(0) // interface{}
|
|
||||||
|
|
||||||
// 获取时间
|
|
||||||
t := list.GetTime(0) // *time.Time
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 向上取整方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
ceilInt := list.GetCeilInt(3)
|
|
||||||
ceilInt64 := list.GetCeilInt64(3)
|
|
||||||
ceilFloat := list.GetCeilFloat64(3)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 操作方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 修改指定位置的值
|
|
||||||
list.Put(0, "new value")
|
|
||||||
|
|
||||||
// 转换为 JSON 字符串
|
|
||||||
jsonStr := list.ToJsonString()
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Obj 类型
|
|
||||||
|
|
||||||
`Obj` 是一个通用的对象包装器,用于链式类型转换,常用于 `Context` 方法的返回值。
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Obj struct {
|
|
||||||
Data interface{} // 原始数据
|
|
||||||
Error // 错误信息
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用示例
|
|
||||||
|
|
||||||
```go
|
|
||||||
obj := &Obj{Data: "123"}
|
|
||||||
|
|
||||||
// 链式类型转换
|
|
||||||
i := obj.ToInt() // 123
|
|
||||||
i64 := obj.ToInt64() // int64(123)
|
|
||||||
f := obj.ToFloat64() // 123.0
|
|
||||||
s := obj.ToStr() // "123"
|
|
||||||
b := obj.ToBool() // true
|
|
||||||
|
|
||||||
// 复杂类型转换
|
|
||||||
m := obj.ToMap() // 尝试转换为 Map
|
|
||||||
sl := obj.ToSlice() // 尝试转换为 Slice
|
|
||||||
arr := obj.ToMapArray() // 转换为 []Map
|
|
||||||
|
|
||||||
// 获取原始值
|
|
||||||
raw := obj.ToObj() // interface{}
|
|
||||||
|
|
||||||
// 获取时间
|
|
||||||
t := obj.ToTime() // *time.Time
|
|
||||||
|
|
||||||
// 向上取整
|
|
||||||
ceil := obj.ToCeilInt()
|
|
||||||
ceil64 := obj.ToCeilInt64()
|
|
||||||
ceilF := obj.ToCeilFloat64()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 在 Context 中的应用
|
|
||||||
|
|
||||||
```go
|
|
||||||
func handler(that *Context) {
|
|
||||||
// ReqData 返回 *Obj,支持链式调用
|
|
||||||
userId := that.ReqData("user_id").ToInt()
|
|
||||||
name := that.ReqData("name").ToStr()
|
|
||||||
|
|
||||||
// Session 也返回 *Obj
|
|
||||||
adminId := that.Session("admin_id").ToInt64()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 类型转换函数
|
|
||||||
|
|
||||||
`common` 包提供了一系列全局类型转换函数。
|
|
||||||
|
|
||||||
### 基础转换
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 转字符串
|
|
||||||
str := ObjToStr(123) // "123"
|
|
||||||
str := ObjToStr(3.14) // "3.14"
|
|
||||||
str := ObjToStr(Map{"a": 1}) // JSON 格式字符串
|
|
||||||
|
|
||||||
// 转整数
|
|
||||||
i := ObjToInt("123") // 123
|
|
||||||
i64 := ObjToInt64("123") // int64(123)
|
|
||||||
|
|
||||||
// 转浮点数
|
|
||||||
f := ObjToFloat64("3.14") // 3.14
|
|
||||||
|
|
||||||
// 转布尔
|
|
||||||
b := ObjToBool(1) // true
|
|
||||||
b := ObjToBool(0) // false
|
|
||||||
|
|
||||||
// 转 Map
|
|
||||||
m := ObjToMap(`{"a": 1}`) // Map{"a": 1}
|
|
||||||
m := ObjToMap(someStruct) // 结构体转 Map
|
|
||||||
|
|
||||||
// 转 Slice
|
|
||||||
s := ObjToSlice(`[1, 2, 3]`) // Slice{1, 2, 3}
|
|
||||||
|
|
||||||
// 转 []Map
|
|
||||||
arr := ObjToMapArray(slice) // []Map
|
|
||||||
```
|
|
||||||
|
|
||||||
### 向上取整转换
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 向上取整后转整数
|
|
||||||
ceil := ObjToCeilInt(3.2) // 4
|
|
||||||
ceil64 := ObjToCeilInt64(3.2) // int64(4)
|
|
||||||
ceilF := ObjToCeilFloat64(3.2) // 4.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### 时间转换
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 自动识别多种格式
|
|
||||||
t := ObjToTime("2024-01-15 10:30:00") // *time.Time
|
|
||||||
t := ObjToTime("2024-01-15") // *time.Time
|
|
||||||
t := ObjToTime(1705298400) // Unix 秒
|
|
||||||
t := ObjToTime(1705298400000) // Unix 毫秒
|
|
||||||
t := ObjToTime(1705298400000000) // Unix 微秒
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字符串转换
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 字符串转 Map
|
|
||||||
m := StrToMap(`{"key": "value"}`)
|
|
||||||
|
|
||||||
// 字符串转 Slice
|
|
||||||
s := StrToSlice(`[1, 2, 3]`)
|
|
||||||
|
|
||||||
// 字符串转 int
|
|
||||||
i, err := StrToInt("123")
|
|
||||||
|
|
||||||
// 字符串数组格式转换
|
|
||||||
jsonArr := StrArrayToJsonStr("a1,a2,a3") // "[a1,a2,a3]"
|
|
||||||
strArr := JsonStrToStrArray("[a1,a2,a3]") // ",a1,a2,a3,"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 错误处理
|
|
||||||
|
|
||||||
所有转换函数支持可选的错误参数:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var e Error
|
|
||||||
i := ObjToInt("abc", &e)
|
|
||||||
if e.GetError() != nil {
|
|
||||||
// 处理转换错误
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 工具函数
|
|
||||||
|
|
||||||
### 字符串处理
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 字符串截取(支持中文)
|
|
||||||
str := Substr("Hello世界", 0, 7) // "Hello世"
|
|
||||||
str := Substr("Hello", -2, 2) // "lo" (负数从末尾计算)
|
|
||||||
|
|
||||||
// 首字母大写
|
|
||||||
upper := StrFirstToUpper("hello") // "Hello"
|
|
||||||
|
|
||||||
// 查找最后出现位置
|
|
||||||
idx := IndexLastStr("a.b.c", ".") // 3
|
|
||||||
|
|
||||||
// 字符串相似度(Levenshtein 距离)
|
|
||||||
dist := StrLd("hello", "hallo", true) // 1 (忽略大小写)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 时间处理
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 时间转字符串
|
|
||||||
str := Time2Str(time.Now()) // "2024-01-15 10:30:00"
|
|
||||||
str := Time2Str(time.Now(), 1) // "2024-01"
|
|
||||||
str := Time2Str(time.Now(), 2) // "2024-01-15"
|
|
||||||
str := Time2Str(time.Now(), 3) // "2024-01-15 10"
|
|
||||||
str := Time2Str(time.Now(), 4) // "2024-01-15 10:30"
|
|
||||||
str := Time2Str(time.Now(), 5) // "2024-01-15 10:30:00"
|
|
||||||
|
|
||||||
// 特殊格式
|
|
||||||
str := Time2Str(time.Now(), 12) // "01-15"
|
|
||||||
str := Time2Str(time.Now(), 14) // "01-15 10:30"
|
|
||||||
str := Time2Str(time.Now(), 34) // "10:30"
|
|
||||||
str := Time2Str(time.Now(), 35) // "10:30:00"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 加密与随机
|
|
||||||
|
|
||||||
```go
|
|
||||||
// MD5 加密
|
|
||||||
hash := Md5("password") // 32位小写MD5
|
|
||||||
|
|
||||||
// 随机数
|
|
||||||
r := Rand(3) // 3位随机数 (0-999)
|
|
||||||
r := RandX(10, 100) // 10-100之间的随机数
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数学计算
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 四舍五入保留小数
|
|
||||||
f := Round(3.14159, 2) // 3.14
|
|
||||||
f := Round(3.145, 2) // 3.15
|
|
||||||
```
|
|
||||||
|
|
||||||
### 深拷贝
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 深拷贝 Map/Slice(递归复制)
|
|
||||||
original := Map{"a": Map{"b": 1}}
|
|
||||||
copied := DeepCopyMap(original).(Map)
|
|
||||||
|
|
||||||
// 修改副本不影响原始数据
|
|
||||||
copied.GetMap("a")["b"] = 2
|
|
||||||
// original["a"]["b"] 仍然是 1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
### Error 类型
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Error struct {
|
|
||||||
Logger *logrus.Logger // 可选的日志记录器
|
|
||||||
error // 内嵌错误
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```go
|
|
||||||
var e Error
|
|
||||||
|
|
||||||
// 设置错误
|
|
||||||
e.SetError(errors.New("something wrong"))
|
|
||||||
|
|
||||||
// 获取错误
|
|
||||||
if err := e.GetError(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配合日志自动记录
|
|
||||||
e.Logger = logrusLogger
|
|
||||||
e.SetError(errors.New("will be logged"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 在类型转换中使用
|
|
||||||
|
|
||||||
```go
|
|
||||||
var e Error
|
|
||||||
data := Map{"count": "abc"}
|
|
||||||
|
|
||||||
count := data.GetInt("count", &e)
|
|
||||||
if e.GetError() != nil {
|
|
||||||
// 转换失败,count = 0
|
|
||||||
fmt.Println("转换失败:", e.GetError())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 链式调用处理请求数据
|
|
||||||
|
|
||||||
```go
|
|
||||||
func handler(that *Context) {
|
|
||||||
// 推荐:使用 Obj 链式调用
|
|
||||||
userId := that.ReqData("user_id").ToInt()
|
|
||||||
page := that.ReqData("page").ToInt()
|
|
||||||
if page < 1 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 Map 数据
|
|
||||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
|
||||||
if user != nil {
|
|
||||||
name := user.GetString("name")
|
|
||||||
age := user.GetInt("age")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 安全的类型转换
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 带错误检查的转换
|
|
||||||
var e Error
|
|
||||||
data := someMap.GetInt("key", &e)
|
|
||||||
if e.GetError() != nil {
|
|
||||||
// 使用默认值
|
|
||||||
data = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单场景直接转换(失败返回零值)
|
|
||||||
data := someMap.GetInt("key") // 失败返回 0
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 处理数据库查询结果
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 查询返回 Map
|
|
||||||
user := that.Db.Get("user", "*", Map{"id": 1})
|
|
||||||
if user != nil {
|
|
||||||
name := user.GetString("name")
|
|
||||||
createTime := user.GetTime("create_time")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询返回 []Map
|
|
||||||
users := that.Db.Select("user", "*", Map{"status": 1})
|
|
||||||
for _, u := range users {
|
|
||||||
fmt.Printf("ID: %d, Name: %s\n", u.GetInt("id"), u.GetString("name"))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关文档
|
|
||||||
|
|
||||||
- [快速上手指南](QUICKSTART.md)
|
|
||||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md)
|
|
||||||
- [代码生成器使用说明](CodeGen_使用说明.md)
|
|
||||||
@ -1,516 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量插入 (Inserts) - 新增
|
|
||||||
```go
|
|
||||||
// 使用 []Map 格式,更直观简洁
|
|
||||||
affected := database.Inserts("table", []Map{
|
|
||||||
{"col1": "val1", "col2": "val2", "col3": "val3"},
|
|
||||||
{"col1": "val4", "col2": "val5", "col3": "val6"},
|
|
||||||
})
|
|
||||||
// 返回受影响的行数
|
|
||||||
|
|
||||||
// 支持 [#] 标记直接 SQL
|
|
||||||
affected := database.Inserts("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.Inserts("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月*
|
|
||||||
|
|
||||||
**详细说明:**
|
|
||||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) - 完整教程
|
|
||||||
@ -1,833 +0,0 @@
|
|||||||
# HoTimeDB ORM 使用说明书
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL等数据库,并集成了缓存、事务、链式查询等功能。
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [快速开始](#快速开始)
|
|
||||||
- [数据库配置](#数据库配置)
|
|
||||||
- [基本操作](#基本操作)
|
|
||||||
- [查询(Select)](#查询select)
|
|
||||||
- [获取单条记录(Get)](#获取单条记录get)
|
|
||||||
- [插入(Insert)](#插入insert)
|
|
||||||
- [批量插入(Inserts)](#批量插入Inserts)
|
|
||||||
- [更新(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)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 批量插入(Inserts)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 批量插入多条记录(使用 []Map 格式,更直观)
|
|
||||||
affected := database.Inserts("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.Inserts("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`、`Inserts` 操作时会自动清除对应表的缓存。
|
|
||||||
|
|
||||||
### 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语言特性进行了适配和优化。
|
|
||||||
|
|
||||||
**更多参考:**
|
|
||||||
- [HoTimeDB API 参考](HoTimeDB_API参考.md) - API 速查手册
|
|
||||||
@ -1,529 +0,0 @@
|
|||||||
# HoTime 快速上手指南
|
|
||||||
|
|
||||||
5 分钟入门 HoTime 框架。
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
```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`
|
|
||||||
|
|
||||||
## 配置文件
|
|
||||||
|
|
||||||
创建 `config/config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"port": "8081",
|
|
||||||
"mode": 2,
|
|
||||||
"sessionName": "HOTIME",
|
|
||||||
"tpt": "tpt",
|
|
||||||
"defFile": ["index.html", "index.htm"],
|
|
||||||
"db": {
|
|
||||||
"mysql": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": "3306",
|
|
||||||
"name": "your_database",
|
|
||||||
"user": "root",
|
|
||||||
"password": "your_password",
|
|
||||||
"prefix": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cache": {
|
|
||||||
"memory": {
|
|
||||||
"db": true,
|
|
||||||
"session": true,
|
|
||||||
"timeout": 7200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 配置项说明
|
|
||||||
|
|
||||||
| 配置项 | 默认值 | 说明 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| `port` | 80 | HTTP 服务端口,0 为不启用 |
|
|
||||||
| `tlsPort` | - | HTTPS 端口,需配合 tlsCert/tlsKey |
|
|
||||||
| `tlsCert` | - | HTTPS 证书路径 |
|
|
||||||
| `tlsKey` | - | HTTPS 密钥路径 |
|
|
||||||
| `mode` | 0 | 0=生产, 1=测试, 2=开发(输出SQL) |
|
|
||||||
| `tpt` | tpt | 静态文件目录 |
|
|
||||||
| `sessionName` | HOTIME | Session Cookie 名称 |
|
|
||||||
| `modeRouterStrict` | false | 路由大小写敏感,false=忽略大小写 |
|
|
||||||
| `crossDomain` | - | 跨域设置,空=不开启,auto=智能开启,或指定域名 |
|
|
||||||
| `logFile` | - | 日志文件路径,如 `logs/20060102.txt` |
|
|
||||||
| `logLevel` | 0 | 日志等级,0=关闭,1=打印 |
|
|
||||||
| `webConnectLogShow` | true | 是否显示访问日志 |
|
|
||||||
| `defFile` | ["index.html"] | 目录默认访问文件 |
|
|
||||||
|
|
||||||
### 数据库配置
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"db": {
|
|
||||||
"mysql": {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": "3306",
|
|
||||||
"name": "database_name",
|
|
||||||
"user": "root",
|
|
||||||
"password": "password",
|
|
||||||
"prefix": "app_",
|
|
||||||
"slave": {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": "3306",
|
|
||||||
"name": "database_name",
|
|
||||||
"user": "root",
|
|
||||||
"password": "password"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sqlite": {
|
|
||||||
"path": "config/data.db",
|
|
||||||
"prefix": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> MySQL 配置 `slave` 项即启用主从读写分离
|
|
||||||
|
|
||||||
### 缓存配置
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"cache": {
|
|
||||||
"memory": {
|
|
||||||
"db": true,
|
|
||||||
"session": true,
|
|
||||||
"timeout": 7200
|
|
||||||
},
|
|
||||||
"redis": {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 6379,
|
|
||||||
"password": "",
|
|
||||||
"db": true,
|
|
||||||
"session": true,
|
|
||||||
"timeout": 1296000
|
|
||||||
},
|
|
||||||
"db": {
|
|
||||||
"db": true,
|
|
||||||
"session": true,
|
|
||||||
"timeout": 2592000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
缓存优先级: **Memory > Redis > DB**,自动穿透与回填
|
|
||||||
|
|
||||||
### 错误码配置
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": {
|
|
||||||
"1": "内部系统异常",
|
|
||||||
"2": "访问权限异常",
|
|
||||||
"3": "请求参数异常",
|
|
||||||
"4": "数据处理异常",
|
|
||||||
"5": "数据结果异常"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> 自定义错误码建议从 10 开始
|
|
||||||
|
|
||||||
## 路由系统
|
|
||||||
|
|
||||||
HoTime 使用三层路由结构:`模块/控制器/方法`
|
|
||||||
|
|
||||||
```go
|
|
||||||
appIns.Run(Router{
|
|
||||||
"模块名": {
|
|
||||||
"控制器名": {
|
|
||||||
"方法名": func(that *Context) {
|
|
||||||
// 处理逻辑
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 路由路径
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 获取路由信息
|
|
||||||
module := that.RouterString[0] // 模块
|
|
||||||
controller := that.RouterString[1] // 控制器
|
|
||||||
action := that.RouterString[2] // 方法
|
|
||||||
|
|
||||||
// 完整请求路径
|
|
||||||
fullPath := that.HandlerStr // 如 /app/user/login
|
|
||||||
```
|
|
||||||
|
|
||||||
## 请求参数获取
|
|
||||||
|
|
||||||
### 新版推荐方法(支持链式调用)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 获取 URL 查询参数 (?id=1)
|
|
||||||
id := that.ReqParam("id").ToInt()
|
|
||||||
name := that.ReqParam("name").ToStr()
|
|
||||||
|
|
||||||
// 获取表单参数 (POST form-data / x-www-form-urlencoded)
|
|
||||||
username := that.ReqForm("username").ToStr()
|
|
||||||
age := that.ReqForm("age").ToInt()
|
|
||||||
|
|
||||||
// 获取 JSON Body 参数 (POST application/json)
|
|
||||||
data := that.ReqJson("data").ToMap()
|
|
||||||
items := that.ReqJson("items").ToSlice()
|
|
||||||
|
|
||||||
// 统一获取(自动判断来源,优先级: JSON > Form > URL)
|
|
||||||
userId := that.ReqData("user_id").ToInt()
|
|
||||||
status := that.ReqData("status").ToStr()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 类型转换方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
obj := that.ReqData("key")
|
|
||||||
|
|
||||||
obj.ToStr() // 转字符串
|
|
||||||
obj.ToInt() // 转 int
|
|
||||||
obj.ToInt64() // 转 int64
|
|
||||||
obj.ToFloat64() // 转 float64
|
|
||||||
obj.ToBool() // 转 bool
|
|
||||||
obj.ToMap() // 转 Map
|
|
||||||
obj.ToSlice() // 转 Slice
|
|
||||||
obj.Data // 获取原始值(interface{})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 文件上传
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 单文件上传
|
|
||||||
file, header, err := that.ReqFile("avatar")
|
|
||||||
if err == nil {
|
|
||||||
defer file.Close()
|
|
||||||
// header.Filename - 文件名
|
|
||||||
// header.Size - 文件大小
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多文件上传(批量)
|
|
||||||
files, err := that.ReqFiles("images")
|
|
||||||
if err == nil {
|
|
||||||
for _, fh := range files {
|
|
||||||
file, _ := fh.Open()
|
|
||||||
defer file.Close()
|
|
||||||
// 处理每个文件
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 传统方法(兼容)
|
|
||||||
|
|
||||||
```go
|
|
||||||
// GET/POST 参数
|
|
||||||
name := that.Req.FormValue("name")
|
|
||||||
|
|
||||||
// URL 参数
|
|
||||||
id := that.Req.URL.Query().Get("id")
|
|
||||||
|
|
||||||
// 请求头
|
|
||||||
token := that.Req.Header.Get("Authorization")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 响应数据
|
|
||||||
|
|
||||||
### Display 方法
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 成功响应 (status=0)
|
|
||||||
that.Display(0, Map{"user": user, "token": token})
|
|
||||||
// 输出: {"status":0, "result":{"user":..., "token":...}}
|
|
||||||
|
|
||||||
// 错误响应 (status>0)
|
|
||||||
that.Display(1, "系统内部错误")
|
|
||||||
// 输出: {"status":1, "result":{"type":"内部系统异常", "msg":"系统内部错误"}, "error":{...}}
|
|
||||||
|
|
||||||
that.Display(2, "请先登录")
|
|
||||||
// 输出: {"status":2, "result":{"type":"访问权限异常", "msg":"请先登录"}, "error":{...}}
|
|
||||||
|
|
||||||
that.Display(3, "参数不能为空")
|
|
||||||
// 输出: {"status":3, "result":{"type":"请求参数异常", "msg":"参数不能为空"}, "error":{...}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 错误码含义
|
|
||||||
|
|
||||||
| 错误码 | 类型 | 使用场景 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 0 | 成功 | 请求成功 |
|
|
||||||
| 1 | 内部系统异常 | 环境配置、文件权限等基础运行环境错误 |
|
|
||||||
| 2 | 访问权限异常 | 未登录或登录异常 |
|
|
||||||
| 3 | 请求参数异常 | 参数不足、类型错误等 |
|
|
||||||
| 4 | 数据处理异常 | 数据库操作或第三方请求返回异常 |
|
|
||||||
| 5 | 数据结果异常 | 无法返回要求的格式 |
|
|
||||||
|
|
||||||
### 自定义响应
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 自定义 Header
|
|
||||||
that.Resp.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// 直接写入
|
|
||||||
that.Resp.Write([]byte("raw data"))
|
|
||||||
|
|
||||||
// 自定义响应函数
|
|
||||||
that.RespFunc = func() {
|
|
||||||
// 自定义响应逻辑
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 中间件
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 全局中间件(请求拦截)
|
|
||||||
appIns.SetConnectListener(func(that *Context) bool {
|
|
||||||
// 放行登录接口
|
|
||||||
if len(that.RouterString) >= 3 && that.RouterString[2] == "login" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查登录状态
|
|
||||||
if that.Session("user_id").Data == nil {
|
|
||||||
that.Display(2, "请先登录")
|
|
||||||
return true // 返回 true 终止请求
|
|
||||||
}
|
|
||||||
return false // 返回 false 继续处理
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Session 与缓存
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Session 操作
|
|
||||||
that.Session("user_id", 123) // 设置
|
|
||||||
userId := that.Session("user_id") // 获取 *Obj
|
|
||||||
that.Session("user_id", nil) // 删除
|
|
||||||
|
|
||||||
// 链式获取
|
|
||||||
id := that.Session("user_id").ToInt64()
|
|
||||||
name := that.Session("username").ToStr()
|
|
||||||
|
|
||||||
// 通用缓存
|
|
||||||
that.Cache("key", "value") // 设置
|
|
||||||
data := that.Cache("key") // 获取
|
|
||||||
that.Cache("key", nil) // 删除
|
|
||||||
```
|
|
||||||
|
|
||||||
三级缓存自动运作:**Memory → Redis → Database**
|
|
||||||
|
|
||||||
## 数据库操作(简要)
|
|
||||||
|
|
||||||
### 基础 CRUD
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 查询列表
|
|
||||||
users := that.Db.Select("user", "*", Map{"status": 1})
|
|
||||||
|
|
||||||
// 查询单条
|
|
||||||
user := that.Db.Get("user", "*", Map{"id": 1})
|
|
||||||
|
|
||||||
// 插入
|
|
||||||
id := that.Db.Insert("user", Map{"name": "test", "age": 18})
|
|
||||||
|
|
||||||
// 批量插入
|
|
||||||
affected := that.Db.Inserts("user", []Map{
|
|
||||||
{"name": "user1", "age": 20},
|
|
||||||
{"name": "user2", "age": 25},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新
|
|
||||||
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").
|
|
||||||
Where("status", 1).
|
|
||||||
And("age[>]", 18).
|
|
||||||
Order("id DESC").
|
|
||||||
Page(1, 10).
|
|
||||||
Select("*")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 条件语法速查
|
|
||||||
|
|
||||||
| 语法 | 说明 | 示例 |
|
|
||||||
|------|------|------|
|
|
||||||
| `key` | 等于 | `"id": 1` |
|
|
||||||
| `key[>]` | 大于 | `"age[>]": 18` |
|
|
||||||
| `key[<]` | 小于 | `"age[<]": 60` |
|
|
||||||
| `key[>=]` | 大于等于 | `"age[>=]": 18` |
|
|
||||||
| `key[<=]` | 小于等于 | `"age[<=]": 60` |
|
|
||||||
| `key[!]` | 不等于 | `"status[!]": 0` |
|
|
||||||
| `key[~]` | LIKE | `"name[~]": "test"` |
|
|
||||||
| `key[<>]` | BETWEEN | `"age[<>]": Slice{18, 60}` |
|
|
||||||
| `key` | IN | `"id": Slice{1, 2, 3}` |
|
|
||||||
|
|
||||||
### 事务
|
|
||||||
|
|
||||||
```go
|
|
||||||
success := that.Db.Action(func(tx db.HoTimeDB) bool {
|
|
||||||
tx.Update("user", Map{"balance[#]": "balance - 100"}, Map{"id": 1})
|
|
||||||
tx.Insert("order", Map{"user_id": 1, "amount": 100})
|
|
||||||
return true // 返回 true 提交,false 回滚
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
> **更多数据库操作**:参见 [HoTimeDB 使用说明](HoTimeDB_使用说明.md)
|
|
||||||
|
|
||||||
## 日志记录
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 创建操作日志(自动插入 logs 表)
|
|
||||||
that.Log = Map{
|
|
||||||
"type": "login",
|
|
||||||
"action": "用户登录",
|
|
||||||
"data": Map{"phone": phone},
|
|
||||||
}
|
|
||||||
// 框架会自动添加 time, admin_id/user_id, ip 等字段
|
|
||||||
```
|
|
||||||
|
|
||||||
## 扩展功能
|
|
||||||
|
|
||||||
| 功能 | 路径 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| 微信支付/公众号/小程序 | `dri/wechat/` | 微信全套 SDK |
|
|
||||||
| 阿里云服务 | `dri/aliyun/` | 企业认证等 |
|
|
||||||
| 腾讯云服务 | `dri/tencent/` | 企业认证等 |
|
|
||||||
| 文件上传 | `dri/upload/` | 文件上传处理 |
|
|
||||||
| 文件下载 | `dri/download/` | 文件下载处理 |
|
|
||||||
| MongoDB | `dri/mongodb/` | MongoDB 驱动 |
|
|
||||||
| RSA 加解密 | `dri/rsa/` | RSA 加解密工具 |
|
|
||||||
|
|
||||||
## 完整示例
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "code.hoteas.com/golang/hotime"
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
appIns := Init("config/config.json")
|
|
||||||
|
|
||||||
// 登录检查中间件
|
|
||||||
appIns.SetConnectListener(func(that *Context) bool {
|
|
||||||
// 放行登录接口
|
|
||||||
if len(that.RouterString) >= 3 && that.RouterString[2] == "login" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if that.Session("user_id").Data == nil {
|
|
||||||
that.Display(2, "请先登录")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
appIns.Run(Router{
|
|
||||||
"api": {
|
|
||||||
"user": {
|
|
||||||
"login": func(that *Context) {
|
|
||||||
phone := that.ReqData("phone").ToStr()
|
|
||||||
password := that.ReqData("password").ToStr()
|
|
||||||
|
|
||||||
if phone == "" || password == "" {
|
|
||||||
that.Display(3, "手机号和密码不能为空")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user := that.Db.Get("user", "*", Map{
|
|
||||||
"phone": phone,
|
|
||||||
"password": Md5(password),
|
|
||||||
})
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
that.Display(3, "账号或密码错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
that.Session("user_id", user.GetInt64("id"))
|
|
||||||
that.Display(0, Map{"user": user})
|
|
||||||
},
|
|
||||||
|
|
||||||
"info": func(that *Context) {
|
|
||||||
userId := that.Session("user_id").ToInt64()
|
|
||||||
user := that.Db.Get("user", "*", Map{"id": userId})
|
|
||||||
that.Display(0, Map{"user": user})
|
|
||||||
},
|
|
||||||
|
|
||||||
"list": func(that *Context) {
|
|
||||||
page := that.ReqData("page").ToInt()
|
|
||||||
if page == 0 {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
users := that.Db.Table("user").
|
|
||||||
Where("status", 1).
|
|
||||||
Order("id DESC").
|
|
||||||
Page(page, 10).
|
|
||||||
Select("id,name,phone,created_at")
|
|
||||||
|
|
||||||
total := that.Db.Count("user", Map{"status": 1})
|
|
||||||
|
|
||||||
that.Display(0, Map{
|
|
||||||
"list": users,
|
|
||||||
"total": total,
|
|
||||||
"page": page,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
"logout": func(that *Context) {
|
|
||||||
that.Session("user_id", nil)
|
|
||||||
that.Display(0, "退出成功")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**下一步**:
|
|
||||||
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) - 完整数据库教程
|
|
||||||
- [HoTimeDB API 参考](HoTimeDB_API参考.md) - API 速查手册
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
142
dri/baidu/map.go
142
dri/baidu/map.go
@ -1,142 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -10,31 +10,29 @@ import (
|
|||||||
//"fmt"
|
//"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dingdongyun struct {
|
type DDY struct {
|
||||||
ApiKey string
|
ApiKey string
|
||||||
YzmUrl string
|
YzmUrl string
|
||||||
TzUrl string
|
TzUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
var DDY = dingdongyun{}
|
func (this *DDY) Init(apikey string) {
|
||||||
|
this.ApiKey = apikey
|
||||||
func (that *dingdongyun) Init(apikey string) {
|
this.YzmUrl = "https://api.dingdongcloud.com/v2/sms/captcha/send.json"
|
||||||
that.ApiKey = apikey
|
this.TzUrl = "https://api.dingdongcloud.com/v2/sms/notice/send.json"
|
||||||
that.YzmUrl = "https://api.dingdongcloud.com/v2/sms/captcha/send.json"
|
|
||||||
that.TzUrl = "https://api.dingdongcloud.com/v2/sms/notice/send.json"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendYZM 发送短信验证码 code验证码如:123456 返回true表示发送成功flase表示发送失败
|
//发送短信验证码 code验证码如:123456 返回true表示发送成功flase表示发送失败
|
||||||
func (that *dingdongyun) SendYZM(umoblie string, tpt string, data map[string]string) (bool, error) {
|
func (this *DDY) SendYZM(umoblie string, tpt string, data map[string]string) (bool, error) {
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return that.send(that.YzmUrl, umoblie, tpt)
|
return this.send(this.YzmUrl, umoblie, tpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTz 发送通知
|
//发送通知
|
||||||
func (that *dingdongyun) SendTz(umoblie []string, tpt string, data map[string]string) (bool, error) {
|
func (this *DDY) SendTz(umoblie []string, tpt string, data map[string]string) (bool, error) {
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
tpt = strings.Replace(tpt, "{"+k+"}", v, -1)
|
||||||
}
|
}
|
||||||
@ -46,14 +44,14 @@ func (that *dingdongyun) SendTz(umoblie []string, tpt string, data map[string]st
|
|||||||
}
|
}
|
||||||
umobleStr += "," + v
|
umobleStr += "," + v
|
||||||
}
|
}
|
||||||
return that.send(that.TzUrl, umobleStr, tpt)
|
return this.send(this.TzUrl, umobleStr, tpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送短信
|
//发送短信
|
||||||
func (that *dingdongyun) send(mUrl string, umoblie string, content string) (bool, error) {
|
func (this *DDY) send(mUrl string, umoblie string, content string) (bool, error) {
|
||||||
|
|
||||||
data_send_sms_yzm := url.Values{"apikey": {that.ApiKey}, "mobile": {umoblie}, "content": {content}}
|
data_send_sms_yzm := url.Values{"apikey": {this.ApiKey}, "mobile": {umoblie}, "content": {content}}
|
||||||
res, err := that.httpsPostForm(mUrl, data_send_sms_yzm)
|
res, err := this.httpsPostForm(mUrl, data_send_sms_yzm)
|
||||||
if err != nil && res == "" {
|
if err != nil && res == "" {
|
||||||
return false, errors.New("连接错误")
|
return false, errors.New("连接错误")
|
||||||
}
|
}
|
||||||
@ -74,8 +72,8 @@ func (that *dingdongyun) send(mUrl string, umoblie string, content string) (bool
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用url发送短信的连接
|
//调用url发送短信的连接
|
||||||
func (that *dingdongyun) httpsPostForm(url string, data url.Values) (string, error) {
|
func (this *DDY) httpsPostForm(url string, data url.Values) (string, error) {
|
||||||
resp, err := http.PostForm(url, data)
|
resp, err := http.PostForm(url, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -91,3 +89,9 @@ func (that *dingdongyun) httpsPostForm(url string, data url.Values) (string, err
|
|||||||
return string(body), nil
|
return string(body), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DefaultDDY DDY
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DefaultDDY = DDY{}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
package download
|
package download
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
. "../../common"
|
||||||
"bytes"
|
"bytes"
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Down 下载文件
|
//下载文件
|
||||||
func Down(url, path, name string, e ...*Error) bool {
|
func Down(url, path, name string, e ...*Error) bool {
|
||||||
|
|
||||||
os.MkdirAll(path, os.ModeDir)
|
os.MkdirAll(path, os.ModeDir)
|
||||||
@ -18,13 +18,13 @@ func Down(url, path, name string, e ...*Error) bool {
|
|||||||
}
|
}
|
||||||
out, err := os.Create(path + name)
|
out, err := os.Create(path + name)
|
||||||
|
|
||||||
if err != nil && len(e) != 0 {
|
if err != nil && e[0] != nil {
|
||||||
e[0].SetError(err)
|
e[0].SetError(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil && len(e) != 0 {
|
if err != nil && e[0] != nil {
|
||||||
e[0].SetError(err)
|
e[0].SetError(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ func Down(url, path, name string, e ...*Error) bool {
|
|||||||
|
|
||||||
pix, err := ioutil.ReadAll(resp.Body)
|
pix, err := ioutil.ReadAll(resp.Body)
|
||||||
_, err = io.Copy(out, bytes.NewReader(pix))
|
_, err = io.Copy(out, bytes.NewReader(pix))
|
||||||
if err != nil && len(e) != 0 {
|
if err != nil && e[0] != nil {
|
||||||
e[0].SetError(err)
|
e[0].SetError(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,149 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -23,7 +23,7 @@ func FileGet(path string) []byte {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA_Encrypt RSA加密
|
//RSA加密
|
||||||
// plainText 要加密的数据
|
// plainText 要加密的数据
|
||||||
// path 公钥匙文件地址
|
// path 公钥匙文件地址
|
||||||
func RSA_Encrypt(plainText []byte, buf []byte) []byte {
|
func RSA_Encrypt(plainText []byte, buf []byte) []byte {
|
||||||
@ -46,7 +46,7 @@ func RSA_Encrypt(plainText []byte, buf []byte) []byte {
|
|||||||
return cipherText
|
return cipherText
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA_Decrypt RSA解密
|
//RSA解密
|
||||||
// cipherText 需要解密的byte数据
|
// cipherText 需要解密的byte数据
|
||||||
// path 私钥文件路径
|
// path 私钥文件路径
|
||||||
func RSA_Decrypt(cipherText []byte, buf []byte) []byte {
|
func RSA_Decrypt(cipherText []byte, buf []byte) []byte {
|
||||||
@ -97,7 +97,7 @@ func Demo() {
|
|||||||
fmt.Println(string(decrypt))
|
fmt.Println(string(decrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateRSAKey 生成RSA私钥和公钥,保存到文件中
|
//生成RSA私钥和公钥,保存到文件中
|
||||||
// bits 证书大小
|
// bits 证书大小
|
||||||
func GenerateRSAKey(bits int, path string) {
|
func GenerateRSAKey(bits int, path string) {
|
||||||
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
|
//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
|
||||||
|
|||||||
@ -1,119 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@ -8,57 +8,16 @@ import (
|
|||||||
ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
|
ocr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ocr/v20181119"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tencent struct {
|
var credential = common.NewCredential(
|
||||||
secretId string
|
"AKIDOgT8cKCQksnY7yKATaYO7j9ORJzSYohP",
|
||||||
secretKey string
|
"GNXgjdN4czA9ya0FNMApVJzTmsmU0KSN",
|
||||||
credential *common.Credential
|
)
|
||||||
}
|
|
||||||
|
|
||||||
var Tencent = tencent{}
|
func OCR(base64Str string) string {
|
||||||
|
|
||||||
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 := profile.NewClientProfile()
|
||||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
client, _ := ocr.NewClient(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 := ocr.NewGeneralAccurateOCRRequest()
|
||||||
|
|
||||||
@ -79,11 +38,11 @@ func (that *tencent) OCR(base64Str string) string {
|
|||||||
return response.ToJsonString()
|
return response.ToJsonString()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *tencent) Qrcode(base64Str string) string {
|
func Qrcode(base64Str string) string {
|
||||||
|
|
||||||
cpf := profile.NewClientProfile()
|
cpf := profile.NewClientProfile()
|
||||||
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
cpf.HttpProfile.Endpoint = "ocr.tencentcloudapi.com"
|
||||||
client, _ := ocr.NewClient(that.credential, "ap-guangzhou", cpf)
|
client, _ := ocr.NewClient(credential, "ap-guangzhou", cpf)
|
||||||
|
|
||||||
request := ocr.NewQrcodeOCRRequest()
|
request := ocr.NewQrcodeOCRRequest()
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package upload
|
package upload
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
. "../../common"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -15,7 +15,7 @@ type Upload struct {
|
|||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (that *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
func (this *Upload) UpFile(Request *http.Request, fieldName, savefilepath, savePath string) (string, error) {
|
||||||
Request.ParseMultipartForm(32 << 20)
|
Request.ParseMultipartForm(32 << 20)
|
||||||
var filePath string
|
var filePath string
|
||||||
files := Request.MultipartForm.File
|
files := Request.MultipartForm.File
|
||||||
@ -36,7 +36,7 @@ func (that *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
|||||||
data := time.Unix(int64(t), 0).Format("2006-01")
|
data := time.Unix(int64(t), 0).Format("2006-01")
|
||||||
path := ""
|
path := ""
|
||||||
if strings.EqualFold(savefilepath, "") {
|
if strings.EqualFold(savefilepath, "") {
|
||||||
path = that.Path + data
|
path = this.Path + data
|
||||||
} else {
|
} else {
|
||||||
path = savefilepath + data
|
path = savefilepath + data
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ func (that *Upload) UpFile(Request *http.Request, fieldName, savefilepath, saveP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
|
filename := time.Unix(int64(t), 0).Format("2006-01-02-15-22-25") + ObjToStr(Rand(6))
|
||||||
filePath = path + "/" + filename + that.Path
|
filePath = path + "/" + filename + this.Path
|
||||||
} else {
|
} else {
|
||||||
filePath = savePath
|
filePath = savePath
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
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
|
|
||||||
//}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
53
example/app/admin.go
Normal file
53
example/app/admin.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var adminCtr = Ctr{
|
||||||
|
"token": func(this *Context) {
|
||||||
|
this.Display(0, this.SessionId)
|
||||||
|
},
|
||||||
|
"test": func(this *Context) {
|
||||||
|
this.Session("id", this.SessionId)
|
||||||
|
},
|
||||||
|
//自带的登录
|
||||||
|
"login": func(this *Context) {
|
||||||
|
|
||||||
|
name := this.Req.FormValue("name")
|
||||||
|
pwd := this.Req.FormValue("password")
|
||||||
|
if len(name) < 2 ||
|
||||||
|
len(pwd) < 3 {
|
||||||
|
this.Display(3, "数据校验不通过")
|
||||||
|
}
|
||||||
|
where := Map{"password": Md5(pwd)}
|
||||||
|
if len(name) == 11 {
|
||||||
|
where["phone"] = name
|
||||||
|
} else {
|
||||||
|
where["name"] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
admin := this.Db.Get("admin", "*", Map{"AND": where})
|
||||||
|
if admin == nil {
|
||||||
|
this.Display(4, "账户密码错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Session("id", admin.GetCeilInt("id"))
|
||||||
|
admin["password"] = nil
|
||||||
|
this.Display(0, admin)
|
||||||
|
|
||||||
|
},
|
||||||
|
"info": func(this *Context) {
|
||||||
|
admin := this.Db.Get("admin", "*", Map{"id": this.Session("id").ToInt()})
|
||||||
|
|
||||||
|
if admin == nil {
|
||||||
|
this.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
admin["password"] = nil
|
||||||
|
|
||||||
|
this.Display(0, admin)
|
||||||
|
},
|
||||||
|
}
|
||||||
29
example/app/init.go
Normal file
29
example/app/init.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Project 管理端项目
|
||||||
|
var Project = Proj{
|
||||||
|
//"user": UserCtr,
|
||||||
|
"product_spot_check": product_spot_checkCtr,
|
||||||
|
"product": productCtr,
|
||||||
|
"admin": adminCtr,
|
||||||
|
"sms": Sms,
|
||||||
|
"material": materialCtr,
|
||||||
|
"material_inout": material_inoutCtr,
|
||||||
|
"produce_product": produce_productCtr,
|
||||||
|
"produce": produceCtr,
|
||||||
|
"product_line": product_lineCtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成随机码的4位随机数
|
||||||
|
func getCode() string {
|
||||||
|
//res := ""
|
||||||
|
//for i := 0; i < 4; i++ {
|
||||||
|
res := ObjToStr(RandX(1000, 9999))
|
||||||
|
//}
|
||||||
|
return res
|
||||||
|
}
|
||||||
213
example/app/material.go
Normal file
213
example/app/material.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var materialCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
|
str, inData := that.MakeCode.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.MakeCode.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) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := that.Req.FormValue("name")
|
||||||
|
img := that.Req.FormValue("img")
|
||||||
|
validity := ObjToInt(that.Req.FormValue("validity"))
|
||||||
|
num := ObjToInt(that.Req.FormValue("num"))
|
||||||
|
rule := that.Req.FormValue("rule")
|
||||||
|
content := that.Req.FormValue("content")
|
||||||
|
description := that.Req.FormValue("description")
|
||||||
|
if name == "" || rule == "" {
|
||||||
|
that.Display(3, "参数不足,请补充参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := Map{
|
||||||
|
"name": name,
|
||||||
|
"img": img,
|
||||||
|
"rule": rule,
|
||||||
|
"admin_id": adminID,
|
||||||
|
"count": 0,
|
||||||
|
"used": 0,
|
||||||
|
"saved": 0,
|
||||||
|
"num": num,
|
||||||
|
"validity": validity,
|
||||||
|
"description": description,
|
||||||
|
"content": content,
|
||||||
|
"create_time": time.Now().Unix(),
|
||||||
|
"modify_time": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
id := that.Db.Insert("material", data)
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(4, "添加材料失败,请重新添加")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["id"] = id
|
||||||
|
|
||||||
|
that.Display(0, data)
|
||||||
|
},
|
||||||
|
"update": func(that *Context) {
|
||||||
|
inData := that.MakeCode.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)
|
||||||
|
},
|
||||||
|
"inout": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := ObjToMap(that.Req.FormValue("data"))
|
||||||
|
|
||||||
|
texts := data.GetSlice("text")
|
||||||
|
textData := []Map{}
|
||||||
|
for k, _ := range texts {
|
||||||
|
v := texts.GetString(k)
|
||||||
|
if len(v) < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := that.Db.Select("material", "name,id,content,rule,num", Map{"content[~]": v[:len(v)/2]})
|
||||||
|
for _, v1 := range vs {
|
||||||
|
if len(textData) == 0 {
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
for _, vt := range textData {
|
||||||
|
if v1.GetString("id") != vt.GetString("id") {
|
||||||
|
|
||||||
|
add := true
|
||||||
|
for _, vt1 := range textData {
|
||||||
|
if vt1.GetCeilInt("id") == v1.GetCeilInt("id") {
|
||||||
|
add = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if add {
|
||||||
|
v1["count"] = 1
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vt["count"] = vt.GetCeilInt("count") + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qrcode := data.GetSlice("qrcode")
|
||||||
|
for k, _ := range qrcode {
|
||||||
|
v := qrcode.GetString(k)
|
||||||
|
if len(v) < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := that.Db.Select("material", "name,id,content,rule,num", Map{"content[~]": v[:len(v)/2]})
|
||||||
|
for _, v1 := range vs {
|
||||||
|
if len(textData) == 0 {
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
for _, vt := range textData {
|
||||||
|
if v1.GetString("id") != vt.GetString("id") {
|
||||||
|
v1["count"] = 1
|
||||||
|
textData = append(textData, v1)
|
||||||
|
} else {
|
||||||
|
vt["count"] = vt.GetCeilInt("count") + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, textData)
|
||||||
|
|
||||||
|
},
|
||||||
|
"search": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := ObjToInt(that.Req.FormValue("page"))
|
||||||
|
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
leftJoin := Map{"[><]admin": "material.admin_id=admin.id"}
|
||||||
|
columnStr := "material.id,material.name,material.img,material.count,material.used,material.saved,material.admin_id,admin.name AS admin_name,material.modify_time,material.state"
|
||||||
|
where := Map{"ORDER": "modify_time DESC"}
|
||||||
|
count := that.Db.Count("material", where)
|
||||||
|
reData := that.Db.Page(page, pageSize).
|
||||||
|
PageSelect("material", leftJoin, columnStr, where)
|
||||||
|
|
||||||
|
that.Display(0, Map{"count": count, "data": reData})
|
||||||
|
},
|
||||||
|
}
|
||||||
188
example/app/material_inout.go
Normal file
188
example/app/material_inout.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var material_inoutCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(3, "请求参数不足,请检查参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Get("material_inout", "*", Map{"id": id})
|
||||||
|
|
||||||
|
if re == nil {
|
||||||
|
that.Display(4, "找不到对应信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, re)
|
||||||
|
},
|
||||||
|
"add": func(that *Context) {
|
||||||
|
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img := that.Req.FormValue("img")
|
||||||
|
rule := that.Req.FormValue("rule")
|
||||||
|
materialId := ObjToInt(that.Req.FormValue("material_id"))
|
||||||
|
produceId := ObjToInt(that.Req.FormValue("produce_id"))
|
||||||
|
count := ObjToInt(that.Req.FormValue("num"))
|
||||||
|
state := ObjToInt(that.Req.FormValue("state"))
|
||||||
|
|
||||||
|
content := that.Req.FormValue("content")
|
||||||
|
description := that.Req.FormValue("description")
|
||||||
|
|
||||||
|
if rule == "" || materialId == 0 || count == 0 {
|
||||||
|
that.Display(3, "参数不足,请补充参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count1 := count
|
||||||
|
if state > 0 {
|
||||||
|
count = -count
|
||||||
|
}
|
||||||
|
|
||||||
|
produce_material := that.Db.Get("produce_material", "id", Map{"AND": Map{"produce_id": produceId, "material_id": materialId}})
|
||||||
|
if produce_material == nil {
|
||||||
|
that.Db.Insert("produce_material", Map{"produce_id": produceId,
|
||||||
|
"create_time": time.Now().Unix(),
|
||||||
|
"modify_time": time.Now().Unix(),
|
||||||
|
"admin_id": adminID,
|
||||||
|
"material_id": materialId})
|
||||||
|
}
|
||||||
|
if state == 0 {
|
||||||
|
|
||||||
|
that.Db.Update("material", Map{"count[#]": "count+" + ObjToStr(count), "saved[#]": "saved+" + ObjToStr(count)}, Map{"id": materialId})
|
||||||
|
} else {
|
||||||
|
that.Db.Update("material", Map{"count[#]": "count" + ObjToStr(count), "used[#]": "used+" + ObjToStr(-count)}, Map{"id": materialId})
|
||||||
|
}
|
||||||
|
|
||||||
|
material := that.Db.Get("material", "*", Map{"id": materialId})
|
||||||
|
data := Map{
|
||||||
|
"img": img,
|
||||||
|
"rule": rule,
|
||||||
|
"admin_id": adminID,
|
||||||
|
"material_id": materialId,
|
||||||
|
"count": count1,
|
||||||
|
"saved": material.GetCeilInt("count"),
|
||||||
|
"create_time": time.Now().Unix(),
|
||||||
|
"modify_time": time.Now().Unix(),
|
||||||
|
"produce_id": produceId,
|
||||||
|
"description": description,
|
||||||
|
"content": content,
|
||||||
|
"state": state,
|
||||||
|
}
|
||||||
|
id := that.Db.Insert("material_inout", data)
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(4, "添加出入库记录失败,请重新添加")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["id"] = id
|
||||||
|
|
||||||
|
that.Display(0, data)
|
||||||
|
},
|
||||||
|
"update": func(that *Context) {
|
||||||
|
inData := that.MakeCode.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) {
|
||||||
|
inData := that.MakeCode.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) {
|
||||||
|
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page := ObjToInt(that.Req.FormValue("page"))
|
||||||
|
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
|
materialId := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
columnStr := "material_inout.id,material_inout.material_id,material.name,material_inout.img,material_inout.count,material_inout.saved,material_inout.admin_id,admin.name AS admin_name,material_inout.modify_time,material_inout.state"
|
||||||
|
leftJoin := Map{"[><]material": "material_inout.material_id=material.id",
|
||||||
|
"[><]admin": "material_inout.admin_id=admin.id",
|
||||||
|
}
|
||||||
|
where := Map{"ORDER": "modify_time DESC"}
|
||||||
|
|
||||||
|
if materialId != 0 {
|
||||||
|
where["material_id"] = materialId
|
||||||
|
}
|
||||||
|
count := that.Db.Count("material_inout", where)
|
||||||
|
reData := that.Db.Page(page, pageSize).
|
||||||
|
PageSelect("material_inout", leftJoin, columnStr, where)
|
||||||
|
|
||||||
|
that.Display(0, Map{"count": count, "data": reData})
|
||||||
|
},
|
||||||
|
}
|
||||||
201
example/app/produce.go
Normal file
201
example/app/produce.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var produceCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
|
str, inData := that.MakeCode.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.MakeCode.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) {
|
||||||
|
inData := that.MakeCode.Add(that.RouterString[1], that.Req)
|
||||||
|
if inData == nil {
|
||||||
|
that.Display(3, "请求参数不足")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Insert(that.RouterString[1], inData)
|
||||||
|
|
||||||
|
if re == 0 {
|
||||||
|
that.Display(4, "无法插入对应数据")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//索引管理,便于检索以及权限
|
||||||
|
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||||
|
index := that.Db.Get(that.RouterString[1], "`index`", Map{"id": inData.Get("parent_id")})
|
||||||
|
inData["index"] = index.GetString("index") + ObjToStr(re) + ","
|
||||||
|
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||||
|
} else if inData.GetString("index") != "" {
|
||||||
|
inData["index"] = "," + ObjToStr(re) + ","
|
||||||
|
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, re)
|
||||||
|
},
|
||||||
|
"update": func(that *Context) {
|
||||||
|
inData := that.MakeCode.Edit(that.RouterString[1], that.Req)
|
||||||
|
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) {
|
||||||
|
inData := that.MakeCode.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, "删除成功")
|
||||||
|
},
|
||||||
|
"check": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := ObjToMap(that.Req.FormValue("data"))
|
||||||
|
|
||||||
|
texts := data.GetSlice("text")
|
||||||
|
textData := []Map{}
|
||||||
|
for k, _ := range texts {
|
||||||
|
v := texts.GetString(k)
|
||||||
|
if len(v) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := that.Db.Select("produce", Map{"[>]product": "produce.product_id=product.id"}, "produce.name,produce.id,produce.product_id,product.name AS product_name,product.rule_check,product.rule_spot_check", Map{"produce.sn[~]": v[:len(v)/2+1]})
|
||||||
|
for _, v1 := range vs {
|
||||||
|
if len(textData) == 0 {
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
for _, vt := range textData {
|
||||||
|
if v1.GetString("id") != vt.GetString("id") {
|
||||||
|
|
||||||
|
add := true
|
||||||
|
for _, vt1 := range textData {
|
||||||
|
if vt1.GetCeilInt("id") == v1.GetCeilInt("id") {
|
||||||
|
add = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if add {
|
||||||
|
v1["count"] = 1
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vt["count"] = vt.GetCeilInt("count") + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qrcode := data.GetSlice("qrcode")
|
||||||
|
for k, _ := range qrcode {
|
||||||
|
v := qrcode.GetString(k)
|
||||||
|
if len(v) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vs := that.Db.Select("produce", Map{"[>]product": "produce.product_id=product.id"}, "produce.name,produce.id,produce.product_id,product.name AS product_name,product.rule_check,product.rule_spot_check", Map{"produce.sn[~]": v[:len(v)/2+1]})
|
||||||
|
for _, v1 := range vs {
|
||||||
|
if len(textData) == 0 {
|
||||||
|
textData = append(textData, v1)
|
||||||
|
}
|
||||||
|
for _, vt := range textData {
|
||||||
|
if v1.GetString("id") != vt.GetString("id") {
|
||||||
|
v1["count"] = 1
|
||||||
|
textData = append(textData, v1)
|
||||||
|
} else {
|
||||||
|
vt["count"] = vt.GetCeilInt("count") + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, textData)
|
||||||
|
|
||||||
|
},
|
||||||
|
"search": func(that *Context) {
|
||||||
|
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
columnStr := "produce.id,produce.sn,produce.name,produce.state,produce.product_id,product.name AS product_name"
|
||||||
|
where := Map{"produce.state[!]": 0, "ORDER": "produce.modify_time DESC"}
|
||||||
|
|
||||||
|
reData := that.Db.Select("produce", Map{"[>]product": "produce.product_id=product.id"}, columnStr, where)
|
||||||
|
|
||||||
|
that.Display(0, reData)
|
||||||
|
},
|
||||||
|
}
|
||||||
96
example/app/produce_product.go
Normal file
96
example/app/produce_product.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var produce_productCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
sn := that.Req.FormValue("sn")
|
||||||
|
if id == 0 && sn == "" {
|
||||||
|
that.Display(3, "请求参数不足,请检查参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
where := Map{}
|
||||||
|
if id != 0 {
|
||||||
|
where["produce_product.id"] = id
|
||||||
|
} else {
|
||||||
|
where["produce_product.sn"] = sn
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Get("produce_product",
|
||||||
|
|
||||||
|
Map{"[><]product": "produce_product.product_id=product.id",
|
||||||
|
"[><]produce": "produce_product.produce_id=produce.id",
|
||||||
|
},
|
||||||
|
"produce_product.id,produce_product.product_id,product.name AS product_name,"+
|
||||||
|
"produce_product.modify_time,produce_product.state,product.rule_spot_check,produce_product.produce_id,produce.name AS produce_name", where)
|
||||||
|
|
||||||
|
if re == nil {
|
||||||
|
that.Display(4, "找不到对应信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, re)
|
||||||
|
},
|
||||||
|
"add": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sn := that.Req.FormValue("sn")
|
||||||
|
product_id := ObjToInt(that.Req.FormValue("product_id"))
|
||||||
|
produce_id := ObjToInt(that.Req.FormValue("produce_id"))
|
||||||
|
product_line_id := ObjToInt(that.Req.FormValue("product_line_id"))
|
||||||
|
//state := ObjToInt(that.Req.FormValue("state"))
|
||||||
|
//rule_check := that.Req.FormValue("rule_check")
|
||||||
|
//description := that.Req.FormValue("description")
|
||||||
|
if sn == "" {
|
||||||
|
that.Display(3, "参数不足,请补充参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := Map{
|
||||||
|
|
||||||
|
"sn": sn,
|
||||||
|
"product_id": product_id,
|
||||||
|
"produce_id": produce_id,
|
||||||
|
"create_time": time.Now().Unix(),
|
||||||
|
"modify_time": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
data1 := ObjToMap(data.ToJsonString())
|
||||||
|
data1["product_line_id"] = product_line_id
|
||||||
|
id := that.Db.Insert("produce_product", data1)
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(4, "添加新成品失败,请重新添加")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//data["id"] = id
|
||||||
|
//data["rule"] = rule_check
|
||||||
|
//data["produce_product_id"] = id
|
||||||
|
//data["state"] = state
|
||||||
|
//data["description"] = description
|
||||||
|
//id = that.Db.Insert("product_check", data)
|
||||||
|
//if id == 0 {
|
||||||
|
// that.Display(4, "添加质检失败,请重新添加")
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
that.Display(0, data)
|
||||||
|
},
|
||||||
|
}
|
||||||
138
example/app/product.go
Normal file
138
example/app/product.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var productCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
|
str, inData := that.MakeCode.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.MakeCode.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) {
|
||||||
|
inData := that.MakeCode.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) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
//抽检更新
|
||||||
|
ruleSpotCheck := that.Req.FormValue("rule_spot_check")
|
||||||
|
if ruleSpotCheck != "" {
|
||||||
|
spotCheckPercentage := ObjToInt(that.Req.FormValue("spot_check_percentage"))
|
||||||
|
if id == 0 || ruleSpotCheck == "" {
|
||||||
|
that.Display(3, "请求参数不足,请检查参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Update("product", Map{"rule_spot_check": ruleSpotCheck, "spot_check_percentage": spotCheckPercentage, "modify_time": time.Now().Unix()}, Map{"id": id})
|
||||||
|
if re == 0 {
|
||||||
|
that.Display(4, "更新失败,无法更新抽检参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//质检更新
|
||||||
|
ruleCheck := that.Req.FormValue("rule_check")
|
||||||
|
if id == 0 || ruleCheck == "" {
|
||||||
|
that.Display(3, "请求参数不足,请检查参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Update("product", Map{"rule_check": ruleCheck, "modify_time": time.Now().Unix()}, Map{"id": id})
|
||||||
|
|
||||||
|
if re == 0 {
|
||||||
|
that.Display(4, "更新失败,无法更新质检参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, "更新成功")
|
||||||
|
},
|
||||||
|
|
||||||
|
"search": func(that *Context) {
|
||||||
|
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := ObjToInt(that.Req.FormValue("page"))
|
||||||
|
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
leftJoin := Map{"[><]admin": "product.admin_id=admin.id"}
|
||||||
|
columnStr := "product.id,product.name,product.img,product.count,product.used,product.saved,product.spot_check_count,product.admin_id,admin.name AS admin_name,product.modify_time,product.state"
|
||||||
|
where := Map{"ORDER": "modify_time DESC"}
|
||||||
|
count := that.Db.Count("product", where)
|
||||||
|
reData := that.Db.Page(page, pageSize).
|
||||||
|
PageSelect("product", leftJoin, columnStr, where)
|
||||||
|
|
||||||
|
that.Display(0, Map{"count": count, "data": reData})
|
||||||
|
},
|
||||||
|
}
|
||||||
146
example/app/product_line.go
Normal file
146
example/app/product_line.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var product_lineCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
data := that.Db.Get("admin", "*", Map{"id": that.Session("admin_id").ToCeilInt()})
|
||||||
|
str, inData := that.MakeCode.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.MakeCode.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) {
|
||||||
|
inData := that.MakeCode.Add(that.RouterString[1], that.Req)
|
||||||
|
if inData == nil {
|
||||||
|
that.Display(3, "请求参数不足")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Insert(that.RouterString[1], inData)
|
||||||
|
|
||||||
|
if re == 0 {
|
||||||
|
that.Display(4, "无法插入对应数据")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//索引管理,便于检索以及权限
|
||||||
|
if inData.Get("parent_id") != nil && inData.GetString("index") != "" {
|
||||||
|
index := that.Db.Get(that.RouterString[1], "`index`", Map{"id": inData.Get("parent_id")})
|
||||||
|
inData["index"] = index.GetString("index") + ObjToStr(re) + ","
|
||||||
|
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||||
|
} else if inData.GetString("index") != "" {
|
||||||
|
inData["index"] = "," + ObjToStr(re) + ","
|
||||||
|
that.Db.Update(that.RouterString[1], Map{"index": inData["index"]}, Map{"id": re})
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, re)
|
||||||
|
},
|
||||||
|
"update": func(that *Context) {
|
||||||
|
inData := that.MakeCode.Edit(that.RouterString[1], that.Req)
|
||||||
|
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) {
|
||||||
|
inData := that.MakeCode.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) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//page := ObjToInt(that.Req.FormValue("page"))
|
||||||
|
//pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
|
//
|
||||||
|
//if page < 1 {
|
||||||
|
// page = 1
|
||||||
|
//}
|
||||||
|
//if pageSize <= 0 {
|
||||||
|
// pageSize = 10
|
||||||
|
//}
|
||||||
|
|
||||||
|
//leftJoin := Map{"[><]admin": "product.admin_id=admin.id"}
|
||||||
|
|
||||||
|
where := Map{"state": 0, "ORDER": "modify_time DESC"}
|
||||||
|
//count := that.Db.Count("product", where)
|
||||||
|
reData := that.Db.Select("product_line", "*", where)
|
||||||
|
|
||||||
|
that.Display(0, reData)
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
138
example/app/product_spot_check.go
Normal file
138
example/app/product_spot_check.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
. "../../../hotime/common"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var product_spot_checkCtr = Ctr{
|
||||||
|
"info": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(3, "请求参数不足,请检查参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
re := that.Db.Get("product_spot_check",
|
||||||
|
|
||||||
|
Map{"[><]product": "product_spot_check.product_id=product.id",
|
||||||
|
"[><]produce": "product_spot_check.produce_id=produce.id",
|
||||||
|
},
|
||||||
|
"id,img,product_id,product.name AS product_name,admin_id,"+
|
||||||
|
"modify_time,state,rule,produce_id,produce.name AS produce_name", Map{"id": id})
|
||||||
|
|
||||||
|
if re == nil {
|
||||||
|
that.Display(4, "找不到对应信息")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
that.Display(0, re)
|
||||||
|
},
|
||||||
|
"add": func(that *Context) {
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//img := that.Req.FormValue("img")
|
||||||
|
sn := that.Req.FormValue("sn")
|
||||||
|
rule := that.Req.FormValue("rule_spot_check")
|
||||||
|
description := that.Req.FormValue("description")
|
||||||
|
produceProductId := ObjToInt(that.Req.FormValue("produce_product_id"))
|
||||||
|
|
||||||
|
//count := ObjToInt(that.Req.FormValue("count"))
|
||||||
|
state := ObjToInt(that.Req.FormValue("state"))
|
||||||
|
if rule == "" || produceProductId == 0 {
|
||||||
|
that.Display(3, "参数不足,请补充参数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
produceProduct := that.Db.Get("produce_product", "*", Map{"id": produceProductId})
|
||||||
|
if produceProduct == nil {
|
||||||
|
that.Display(4, "找不到成品记录,无法进行抽检")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//判断是否已经抽检了
|
||||||
|
alreadyCheck := that.Db.Get("product_spot_check", "id", Map{"produce_product_id": produceProductId})
|
||||||
|
|
||||||
|
if alreadyCheck == nil {
|
||||||
|
|
||||||
|
that.Db.Update("product", Map{"spot_check_count[#]": "spot_check_count+1"},
|
||||||
|
Map{"id": produceProduct.GetCeilInt("product_id")})
|
||||||
|
|
||||||
|
that.Db.Update("produce", Map{"spot_check_count[#]": "spot_check_count+1"},
|
||||||
|
Map{"id": produceProduct.GetCeilInt("produce_id")})
|
||||||
|
}
|
||||||
|
|
||||||
|
data := Map{
|
||||||
|
"sn": sn,
|
||||||
|
"rule": rule,
|
||||||
|
"admin_id": adminID,
|
||||||
|
"create_time": time.Now().Unix(),
|
||||||
|
"modify_time": time.Now().Unix(),
|
||||||
|
"product_id": produceProduct.GetCeilInt("product_id"),
|
||||||
|
"produce_id": produceProduct.GetCeilInt("produce_id"),
|
||||||
|
"produce_product_id": produceProductId,
|
||||||
|
"description": description,
|
||||||
|
"state": state,
|
||||||
|
}
|
||||||
|
|
||||||
|
id := that.Db.Insert("product_spot_check", data)
|
||||||
|
if id == 0 {
|
||||||
|
that.Display(4, "添加抽检记录失败,请重新添加")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["id"] = id
|
||||||
|
|
||||||
|
that.Display(0, data)
|
||||||
|
},
|
||||||
|
"search": func(that *Context) {
|
||||||
|
|
||||||
|
adminID := that.Session("id").ToInt()
|
||||||
|
|
||||||
|
if adminID == 0 {
|
||||||
|
that.Display(2, "登录失效,请重新登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
page := ObjToInt(that.Req.FormValue("page"))
|
||||||
|
pageSize := ObjToInt(that.Req.FormValue("pageSize"))
|
||||||
|
productId := ObjToInt(that.Req.FormValue("id"))
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
columnStr := "product_spot_check.id,product_spot_check.product_id,product_spot_check.sn,product.name,product_spot_check.img,product_spot_check.admin_id,admin.name AS admin_name,product_spot_check.modify_time,product_spot_check.state"
|
||||||
|
leftJoin := Map{"[><]product": "product_spot_check.product_id=product.id",
|
||||||
|
"[><]admin": "product_spot_check.admin_id=admin.id",
|
||||||
|
}
|
||||||
|
|
||||||
|
where := Map{"ORDER": "id DESC"}
|
||||||
|
|
||||||
|
if productId != 0 {
|
||||||
|
where["product_id"] = productId
|
||||||
|
}
|
||||||
|
|
||||||
|
count := that.Db.Count("product_spot_check", where)
|
||||||
|
|
||||||
|
reData := that.Db.Page(page, pageSize).
|
||||||
|
PageSelect("product_spot_check", leftJoin, columnStr, where)
|
||||||
|
|
||||||
|
that.Display(0, Map{"count": count, "data": reData})
|
||||||
|
},
|
||||||
|
}
|
||||||
33
example/app/sms.go
Normal file
33
example/app/sms.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "../../../hotime"
|
||||||
|
"../../dri/ddsms"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Sms = Ctr{
|
||||||
|
//只允许微信验证过的或者登录成功的发送短信
|
||||||
|
"send": func(this *Context) {
|
||||||
|
//if this.Session("uid").Data == nil && this.Session("wechatInfo").Data == nil {
|
||||||
|
// this.Display(2, "没有授权")
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
if len(this.Req.FormValue("token")) != 32 {
|
||||||
|
this.Display(2, "没有授权")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
phone := this.Req.FormValue("phone")
|
||||||
|
if len(phone) < 11 {
|
||||||
|
this.Display(3, "手机号格式错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
code := getCode()
|
||||||
|
this.Session("phone", phone)
|
||||||
|
this.Session("code", code)
|
||||||
|
|
||||||
|
ddsms.DefaultDDY.SendYZM(phone, this.Config.GetString("smsLogin"), map[string]string{"code": code})
|
||||||
|
|
||||||
|
this.Display(0, "发送成功")
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,414 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
@ -1,327 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
BIN
example/bzyy.exe
Normal file
BIN
example/bzyy.exe
Normal file
Binary file not shown.
3285
example/config/app.json
Normal file
3285
example/config/app.json
Normal file
File diff suppressed because it is too large
Load Diff
49
example/config/config.json
Normal file
49
example/config/config.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"cache": {
|
||||||
|
"db": {
|
||||||
|
"db": false,
|
||||||
|
"session": true
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"db": true,
|
||||||
|
"session": true,
|
||||||
|
"timeout": 7200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"codeConfig": {
|
||||||
|
"admin": "config/app.json"
|
||||||
|
},
|
||||||
|
"codeConfig1": {
|
||||||
|
"admin": {
|
||||||
|
"config": "config/app.json",
|
||||||
|
"package": "admin",
|
||||||
|
"rule": "config/rule.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crossDomain": "auto",
|
||||||
|
"db": {
|
||||||
|
"mysql": {
|
||||||
|
"host": "192.168.6.253",
|
||||||
|
"name": "myhs",
|
||||||
|
"password": "dasda8454456",
|
||||||
|
"port": "3306",
|
||||||
|
"prefix": "",
|
||||||
|
"user": "root"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defFile": [
|
||||||
|
"index.html",
|
||||||
|
"index.htm"
|
||||||
|
],
|
||||||
|
"error": {
|
||||||
|
"1": "内部系统异常",
|
||||||
|
"2": "访问权限异常",
|
||||||
|
"3": "请求参数异常",
|
||||||
|
"4": "数据处理异常",
|
||||||
|
"5": "数据结果异常"
|
||||||
|
},
|
||||||
|
"mode": 3,
|
||||||
|
"port": "8080",
|
||||||
|
"sessionName": "HOTIME",
|
||||||
|
"tpt": "tpt"
|
||||||
|
}
|
||||||
72
example/config/configNote.json
Normal file
72
example/config/configNote.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
"packageName": "默认无,必须,包名称以及应用名,生成代码的配置文件地址,比如config/app.json,数据库有更新时自动更新配置文件以及对应的生成文件",
|
||||||
|
"注释": "配置即启用,非必须,默认无"
|
||||||
|
},
|
||||||
|
"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开启此功能"
|
||||||
|
}
|
||||||
BIN
example/config/data.db
Normal file
BIN
example/config/data.db
Normal file
Binary file not shown.
908
example/main.go
908
example/main.go
@ -1,905 +1,31 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime"
|
"../../hotime"
|
||||||
|
"../dri/ddsms"
|
||||||
|
"./app"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
. "code.hoteas.com/golang/hotime/db"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
appIns := Init("config/config.json")
|
date, _ := time.Parse("2006-01-02 15:04", time.Now().Format("2006-01-02")+" 14:00")
|
||||||
appIns.SetConnectListener(func(that *Context) (isFinished bool) {
|
fmt.Println(date, date.Unix())
|
||||||
return isFinished
|
//fmt.Println("0123456"[1:7])
|
||||||
|
appIns := hotime.Init("config/config.json")
|
||||||
|
//RESTfull接口适配
|
||||||
|
appIns.SetConnectListener(func(context *hotime.Context) bool {
|
||||||
|
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
appIns.Run(Router{
|
//makeCode := code.MakeCode{}
|
||||||
"app": {
|
//fmt.Println(common.ObjToStr(makeCode.Db2JSON("admin","test",appIns.Db)))
|
||||||
"test": {
|
if ddsms.DefaultDDY.ApiKey == "" {
|
||||||
// 测试入口 - 运行所有测试
|
ddsms.DefaultDDY.Init(appIns.Config.GetString("smsKey"))
|
||||||
"all": func(that *Context) {
|
|
||||||
results := Map{}
|
|
||||||
|
|
||||||
// 初始化测试表
|
|
||||||
initTestTables(that)
|
|
||||||
|
|
||||||
// 1. 基础 CRUD 测试
|
|
||||||
results["1_basic_crud"] = testBasicCRUD(that)
|
|
||||||
|
|
||||||
// 2. 条件查询语法测试
|
|
||||||
results["2_condition_syntax"] = testConditionSyntax(that)
|
|
||||||
|
|
||||||
// 3. 链式查询测试
|
|
||||||
results["3_chain_query"] = testChainQuery(that)
|
|
||||||
|
|
||||||
// 4. JOIN 查询测试
|
|
||||||
results["4_join_query"] = testJoinQuery(that)
|
|
||||||
|
|
||||||
// 5. 聚合函数测试
|
|
||||||
results["5_aggregate"] = testAggregate(that)
|
|
||||||
|
|
||||||
// 6. 分页查询测试
|
|
||||||
results["6_pagination"] = testPagination(that)
|
|
||||||
|
|
||||||
// 7. 批量插入测试
|
|
||||||
results["7_batch_insert"] = testInserts(that)
|
|
||||||
|
|
||||||
// 8. Upsert 测试
|
|
||||||
results["8_upsert"] = testUpsert(that)
|
|
||||||
|
|
||||||
// 9. 事务测试
|
|
||||||
results["9_transaction"] = testTransaction(that)
|
|
||||||
|
|
||||||
// 10. 原生 SQL 测试
|
|
||||||
results["10_raw_sql"] = testRawSQL(that)
|
|
||||||
|
|
||||||
that.Display(0, results)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 查询数据库表结构
|
|
||||||
"tables": func(that *Context) {
|
|
||||||
// 查询所有表
|
|
||||||
tables := that.Db.Query("SHOW TABLES")
|
|
||||||
that.Display(0, Map{"tables": tables})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 查询指定表的结构
|
|
||||||
"describe": func(that *Context) {
|
|
||||||
tableName := that.Req.FormValue("table")
|
|
||||||
if tableName == "" {
|
|
||||||
that.Display(1, "请提供 table 参数")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// 查询表结构
|
|
||||||
columns := that.Db.Query("DESCRIBE " + tableName)
|
|
||||||
// 查询表数据(前10条)
|
|
||||||
data := that.Db.Select(tableName, Map{"LIMIT": 10})
|
|
||||||
that.Display(0, Map{"table": tableName, "columns": columns, "sample_data": data})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 单独测试入口
|
appIns.Run(hotime.Router{
|
||||||
"crud": func(that *Context) { that.Display(0, testBasicCRUD(that)) },
|
"app": app.Project,
|
||||||
"condition": func(that *Context) { that.Display(0, testConditionSyntax(that)) },
|
|
||||||
"chain": func(that *Context) { that.Display(0, testChainQuery(that)) },
|
|
||||||
"join": func(that *Context) { that.Display(0, testJoinQuery(that)) },
|
|
||||||
"aggregate": func(that *Context) { that.Display(0, testAggregate(that)) },
|
|
||||||
"pagination": func(that *Context) { that.Display(0, testPagination(that)) },
|
|
||||||
"batch": func(that *Context) { that.Display(0, testInserts(that)) },
|
|
||||||
"upsert": func(that *Context) { that.Display(0, testUpsert(that)) },
|
|
||||||
"transaction": func(that *Context) { that.Display(0, testTransaction(that)) },
|
|
||||||
"rawsql": func(that *Context) { that.Display(0, testRawSQL(that)) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// initTestTables 初始化测试表(MySQL真实数据库)
|
|
||||||
func initTestTables(that *Context) {
|
|
||||||
// MySQL 真实数据库已有表,创建测试用的临时表
|
|
||||||
// 创建测试用的批量插入表
|
|
||||||
that.Db.Exec(`CREATE TABLE IF NOT EXISTS test_batch (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name VARCHAR(100),
|
|
||||||
title VARCHAR(200),
|
|
||||||
state INT DEFAULT 0,
|
|
||||||
create_time DATETIME
|
|
||||||
)`)
|
|
||||||
|
|
||||||
// 检查 admin 表数据(确认表存在)
|
|
||||||
_ = that.Db.Count("admin")
|
|
||||||
_ = that.Db.Count("article")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 1. 基础 CRUD 测试 ====================
|
|
||||||
func testBasicCRUD(that *Context) Map {
|
|
||||||
result := Map{"name": "基础CRUD测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 1.1 Insert 测试 - 使用 admin 表
|
|
||||||
insertTest := Map{"name": "Insert 插入测试 (admin表)"}
|
|
||||||
adminId := that.Db.Insert("admin", Map{
|
|
||||||
"name": "测试管理员_" + fmt.Sprintf("%d", time.Now().Unix()),
|
|
||||||
"phone": fmt.Sprintf("138%d", time.Now().Unix()%100000000),
|
|
||||||
"state": 1,
|
|
||||||
"password": "test123456",
|
|
||||||
"role_id": 1,
|
|
||||||
"title": "测试职位",
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
"modify_time[#]": "NOW()",
|
|
||||||
})
|
|
||||||
insertTest["result"] = adminId > 0
|
|
||||||
insertTest["adminId"] = adminId
|
|
||||||
insertTest["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, insertTest)
|
|
||||||
|
|
||||||
// 1.2 Get 测试
|
|
||||||
getTest := Map{"name": "Get 获取单条记录测试"}
|
|
||||||
admin := that.Db.Get("admin", "*", Map{"id": adminId})
|
|
||||||
getTest["result"] = admin != nil && admin.GetInt64("id") == adminId
|
|
||||||
getTest["admin"] = admin
|
|
||||||
tests = append(tests, getTest)
|
|
||||||
|
|
||||||
// 1.3 Select 测试 - 单条件
|
|
||||||
selectTest1 := Map{"name": "Select 单条件查询测试"}
|
|
||||||
admins1 := that.Db.Select("admin", "*", Map{"state": 1, "LIMIT": 5})
|
|
||||||
selectTest1["result"] = len(admins1) >= 0 // 可能表中没有数据
|
|
||||||
selectTest1["count"] = len(admins1)
|
|
||||||
tests = append(tests, selectTest1)
|
|
||||||
|
|
||||||
// 1.4 Select 测试 - 多条件(自动 AND)
|
|
||||||
selectTest2 := Map{"name": "Select 多条件自动AND测试"}
|
|
||||||
admins2 := that.Db.Select("admin", "*", Map{
|
|
||||||
"state": 1,
|
|
||||||
"role_id[>]": 0,
|
|
||||||
"ORDER": "id DESC",
|
|
||||||
"LIMIT": 5,
|
|
||||||
})
|
|
||||||
selectTest2["result"] = true // 只要不报错就算成功
|
|
||||||
selectTest2["count"] = len(admins2)
|
|
||||||
selectTest2["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, selectTest2)
|
|
||||||
|
|
||||||
// 1.5 Update 测试
|
|
||||||
updateTest := Map{"name": "Update 更新测试"}
|
|
||||||
affected := that.Db.Update("admin", Map{
|
|
||||||
"title": "更新后的职位",
|
|
||||||
"modify_time[#]": "NOW()",
|
|
||||||
}, Map{"id": adminId})
|
|
||||||
updateTest["result"] = affected > 0
|
|
||||||
updateTest["affected"] = affected
|
|
||||||
tests = append(tests, updateTest)
|
|
||||||
|
|
||||||
// 1.6 Delete 测试 - 使用 test_batch 表
|
|
||||||
deleteTest := Map{"name": "Delete 删除测试"}
|
|
||||||
// 先创建一个临时记录用于删除
|
|
||||||
tempId := that.Db.Insert("test_batch", Map{
|
|
||||||
"name": "临时删除测试",
|
|
||||||
"title": "测试标题",
|
|
||||||
"state": 1,
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
})
|
|
||||||
deleteAffected := that.Db.Delete("test_batch", Map{"id": tempId})
|
|
||||||
deleteTest["result"] = deleteAffected > 0
|
|
||||||
deleteTest["affected"] = deleteAffected
|
|
||||||
tests = append(tests, deleteTest)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 2. 条件查询语法测试 ====================
|
|
||||||
func testConditionSyntax(that *Context) Map {
|
|
||||||
result := Map{"name": "条件查询语法测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 2.1 等于 (=) - 使用 article 表
|
|
||||||
test1 := Map{"name": "等于条件 (=)"}
|
|
||||||
articles1 := that.Db.Select("article", "id,title", Map{"state": 0, "LIMIT": 3})
|
|
||||||
test1["result"] = true
|
|
||||||
test1["count"] = len(articles1)
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 2.2 不等于 ([!])
|
|
||||||
test2 := Map{"name": "不等于条件 ([!])"}
|
|
||||||
articles2 := that.Db.Select("article", "id,title,state", Map{"state[!]": -1, "LIMIT": 3})
|
|
||||||
test2["result"] = true
|
|
||||||
test2["count"] = len(articles2)
|
|
||||||
test2["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 2.3 大于 ([>]) 和 小于 ([<])
|
|
||||||
test3 := Map{"name": "大于小于条件 ([>], [<])"}
|
|
||||||
articles3 := that.Db.Select("article", "id,title,click_num", Map{
|
|
||||||
"click_num[>]": 0,
|
|
||||||
"click_num[<]": 100000,
|
|
||||||
"LIMIT": 3,
|
|
||||||
})
|
|
||||||
test3["result"] = true
|
|
||||||
test3["count"] = len(articles3)
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// 2.4 大于等于 ([>=]) 和 小于等于 ([<=])
|
|
||||||
test4 := Map{"name": "大于等于小于等于条件 ([>=], [<=])"}
|
|
||||||
articles4 := that.Db.Select("article", "id,title,sort", Map{
|
|
||||||
"sort[>=]": 0,
|
|
||||||
"sort[<=]": 100,
|
|
||||||
"LIMIT": 3,
|
|
||||||
})
|
|
||||||
test4["result"] = true
|
|
||||||
test4["count"] = len(articles4)
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
// 2.5 LIKE 模糊查询 ([~])
|
|
||||||
test5 := Map{"name": "LIKE 模糊查询 ([~])"}
|
|
||||||
articles5 := that.Db.Select("article", "id,title", Map{"title[~]": "新闻", "LIMIT": 3})
|
|
||||||
test5["result"] = true
|
|
||||||
test5["count"] = len(articles5)
|
|
||||||
test5["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test5)
|
|
||||||
|
|
||||||
// 2.6 右模糊 ([~!])
|
|
||||||
test6 := Map{"name": "右模糊查询 ([~!])"}
|
|
||||||
articles6 := that.Db.Select("admin", "id,name", Map{"name[~!]": "管理", "LIMIT": 3})
|
|
||||||
test6["result"] = true
|
|
||||||
test6["count"] = len(articles6)
|
|
||||||
tests = append(tests, test6)
|
|
||||||
|
|
||||||
// 2.7 BETWEEN ([<>])
|
|
||||||
test7 := Map{"name": "BETWEEN 区间查询 ([<>])"}
|
|
||||||
articles7 := that.Db.Select("article", "id,title,click_num", Map{"click_num[<>]": Slice{0, 1000}, "LIMIT": 3})
|
|
||||||
test7["result"] = true
|
|
||||||
test7["count"] = len(articles7)
|
|
||||||
test7["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test7)
|
|
||||||
|
|
||||||
// 2.8 NOT BETWEEN ([><])
|
|
||||||
test8 := Map{"name": "NOT BETWEEN 查询 ([><])"}
|
|
||||||
articles8 := that.Db.Select("article", "id,title,sort", Map{"sort[><]": Slice{-10, 0}, "LIMIT": 3})
|
|
||||||
test8["result"] = true
|
|
||||||
test8["count"] = len(articles8)
|
|
||||||
tests = append(tests, test8)
|
|
||||||
|
|
||||||
// 2.9 IN 查询
|
|
||||||
test9 := Map{"name": "IN 查询"}
|
|
||||||
articles9 := that.Db.Select("article", "id,title", Map{"id": Slice{1, 2, 3, 4, 5}, "LIMIT": 5})
|
|
||||||
test9["result"] = true
|
|
||||||
test9["count"] = len(articles9)
|
|
||||||
test9["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test9)
|
|
||||||
|
|
||||||
// 2.10 NOT IN ([!])
|
|
||||||
test10 := Map{"name": "NOT IN 查询 ([!])"}
|
|
||||||
articles10 := that.Db.Select("article", "id,title", Map{"id[!]": Slice{1, 2, 3}, "LIMIT": 5})
|
|
||||||
test10["result"] = true
|
|
||||||
test10["count"] = len(articles10)
|
|
||||||
tests = append(tests, test10)
|
|
||||||
|
|
||||||
// 2.11 IS NULL
|
|
||||||
test11 := Map{"name": "IS NULL 查询"}
|
|
||||||
articles11 := that.Db.Select("article", "id,title,img", Map{"img": nil, "LIMIT": 3})
|
|
||||||
test11["result"] = true
|
|
||||||
test11["count"] = len(articles11)
|
|
||||||
tests = append(tests, test11)
|
|
||||||
|
|
||||||
// 2.12 IS NOT NULL ([!])
|
|
||||||
test12 := Map{"name": "IS NOT NULL 查询 ([!])"}
|
|
||||||
articles12 := that.Db.Select("article", "id,title,create_time", Map{"create_time[!]": nil, "LIMIT": 3})
|
|
||||||
test12["result"] = true
|
|
||||||
test12["count"] = len(articles12)
|
|
||||||
tests = append(tests, test12)
|
|
||||||
|
|
||||||
// 2.13 直接 SQL ([##] 用于 SQL 片段)
|
|
||||||
test13 := Map{"name": "直接 SQL 片段查询 ([##])"}
|
|
||||||
articles13 := that.Db.Select("article", "id,title,create_time", Map{
|
|
||||||
"[##]": "create_time > DATE_SUB(NOW(), INTERVAL 365 DAY)",
|
|
||||||
"LIMIT": 3,
|
|
||||||
})
|
|
||||||
test13["result"] = true
|
|
||||||
test13["count"] = len(articles13)
|
|
||||||
test13["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test13)
|
|
||||||
|
|
||||||
// 2.14 显式 AND 条件
|
|
||||||
test14 := Map{"name": "显式 AND 条件"}
|
|
||||||
articles14 := that.Db.Select("article", "id,title,state,click_num", Map{
|
|
||||||
"AND": Map{
|
|
||||||
"state": 0,
|
|
||||||
"click_num[>=]": 0,
|
|
||||||
},
|
|
||||||
"LIMIT": 3,
|
|
||||||
})
|
|
||||||
test14["result"] = true
|
|
||||||
test14["count"] = len(articles14)
|
|
||||||
tests = append(tests, test14)
|
|
||||||
|
|
||||||
// 2.15 OR 条件
|
|
||||||
test15 := Map{"name": "OR 条件"}
|
|
||||||
articles15 := that.Db.Select("article", "id,title,sort,click_num", Map{
|
|
||||||
"OR": Map{
|
|
||||||
"sort": 0,
|
|
||||||
"click_num[>]": 10,
|
|
||||||
},
|
|
||||||
"LIMIT": 5,
|
|
||||||
})
|
|
||||||
test15["result"] = true
|
|
||||||
test15["count"] = len(articles15)
|
|
||||||
tests = append(tests, test15)
|
|
||||||
|
|
||||||
// 2.16 嵌套 AND/OR 条件
|
|
||||||
test16 := Map{"name": "嵌套 AND/OR 条件"}
|
|
||||||
articles16 := that.Db.Select("article", "id,title,sort,state", Map{
|
|
||||||
"AND": Map{
|
|
||||||
"state": 0,
|
|
||||||
"OR": Map{
|
|
||||||
"sort[>=]": 0,
|
|
||||||
"click_num[>]": 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"LIMIT": 5,
|
|
||||||
})
|
|
||||||
test16["result"] = true
|
|
||||||
test16["count"] = len(articles16)
|
|
||||||
test16["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test16)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 3. 链式查询测试 ====================
|
|
||||||
func testChainQuery(that *Context) Map {
|
|
||||||
result := Map{"name": "链式查询测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 3.1 基本链式查询 - 使用 article 表
|
|
||||||
test1 := Map{"name": "基本链式查询 Table().Where().Select()"}
|
|
||||||
articles1 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Select("id,title,author")
|
|
||||||
test1["result"] = len(articles1) >= 0
|
|
||||||
test1["count"] = len(articles1)
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 3.2 链式 And 条件
|
|
||||||
test2 := Map{"name": "链式 And 条件"}
|
|
||||||
articles2 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
And("click_num[>=]", 0).
|
|
||||||
And("sort[>=]", 0).
|
|
||||||
Select("id,title,click_num")
|
|
||||||
test2["result"] = len(articles2) >= 0
|
|
||||||
test2["count"] = len(articles2)
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 3.3 链式 Or 条件
|
|
||||||
test3 := Map{"name": "链式 Or 条件"}
|
|
||||||
articles3 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Or(Map{
|
|
||||||
"sort": 0,
|
|
||||||
"click_num[>]": 10,
|
|
||||||
}).
|
|
||||||
Select("id,title,sort,click_num")
|
|
||||||
test3["result"] = len(articles3) >= 0
|
|
||||||
test3["count"] = len(articles3)
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// 3.4 链式 Order
|
|
||||||
test4 := Map{"name": "链式 Order 排序"}
|
|
||||||
articles4 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Order("create_time DESC", "id ASC").
|
|
||||||
Limit(0, 5).
|
|
||||||
Select("id,title,create_time")
|
|
||||||
test4["result"] = len(articles4) >= 0
|
|
||||||
test4["count"] = len(articles4)
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
// 3.5 链式 Limit
|
|
||||||
test5 := Map{"name": "链式 Limit 限制"}
|
|
||||||
articles5 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Limit(0, 3).
|
|
||||||
Select("id,title")
|
|
||||||
test5["result"] = len(articles5) <= 3
|
|
||||||
test5["count"] = len(articles5)
|
|
||||||
tests = append(tests, test5)
|
|
||||||
|
|
||||||
// 3.6 链式 Get 单条
|
|
||||||
test6 := Map{"name": "链式 Get 获取单条"}
|
|
||||||
article6 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Get("id,title,author")
|
|
||||||
test6["result"] = article6 != nil || true // 允许为空
|
|
||||||
test6["article"] = article6
|
|
||||||
tests = append(tests, test6)
|
|
||||||
|
|
||||||
// 3.7 链式 Count
|
|
||||||
test7 := Map{"name": "链式 Count 统计"}
|
|
||||||
count7 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Count()
|
|
||||||
test7["result"] = count7 >= 0
|
|
||||||
test7["count"] = count7
|
|
||||||
tests = append(tests, test7)
|
|
||||||
|
|
||||||
// 3.8 链式 Page 分页
|
|
||||||
test8 := Map{"name": "链式 Page 分页"}
|
|
||||||
articles8 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Page(1, 5).
|
|
||||||
Select("id,title")
|
|
||||||
test8["result"] = len(articles8) <= 5
|
|
||||||
test8["count"] = len(articles8)
|
|
||||||
tests = append(tests, test8)
|
|
||||||
|
|
||||||
// 3.9 链式 Group 分组
|
|
||||||
test9 := Map{"name": "链式 Group 分组"}
|
|
||||||
stats9 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Group("ctg_id").
|
|
||||||
Select("ctg_id, COUNT(*) as cnt")
|
|
||||||
test9["result"] = len(stats9) >= 0
|
|
||||||
test9["stats"] = stats9
|
|
||||||
tests = append(tests, test9)
|
|
||||||
|
|
||||||
// 3.10 链式 Update
|
|
||||||
test10 := Map{"name": "链式 Update 更新"}
|
|
||||||
// 先获取一个文章ID
|
|
||||||
testArticle := that.Db.Table("article").Where("state", 0).Get("id")
|
|
||||||
if testArticle != nil {
|
|
||||||
affected := that.Db.Table("article").
|
|
||||||
Where("id", testArticle.GetInt64("id")).
|
|
||||||
Update(Map{"modify_time[#]": "NOW()"})
|
|
||||||
test10["result"] = affected >= 0
|
|
||||||
test10["affected"] = affected
|
|
||||||
} else {
|
|
||||||
test10["result"] = true
|
|
||||||
test10["note"] = "无可用测试数据"
|
|
||||||
}
|
|
||||||
tests = append(tests, test10)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 4. JOIN 查询测试 ====================
|
|
||||||
func testJoinQuery(that *Context) Map {
|
|
||||||
result := Map{"name": "JOIN查询测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 4.1 LEFT JOIN 链式 - article 关联 ctg
|
|
||||||
test1 := Map{"name": "LEFT JOIN 链式查询"}
|
|
||||||
articles1 := that.Db.Table("article").
|
|
||||||
LeftJoin("ctg", "article.ctg_id = ctg.id").
|
|
||||||
Where("article.state", 0).
|
|
||||||
Limit(0, 5).
|
|
||||||
Select("article.id, article.title, ctg.name as ctg_name")
|
|
||||||
test1["result"] = len(articles1) >= 0
|
|
||||||
test1["count"] = len(articles1)
|
|
||||||
test1["data"] = articles1
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 4.2 传统 JOIN 语法
|
|
||||||
test2 := Map{"name": "传统 JOIN 语法"}
|
|
||||||
articles2 := that.Db.Select("article",
|
|
||||||
Slice{
|
|
||||||
Map{"[>]ctg": "article.ctg_id = ctg.id"},
|
|
||||||
},
|
|
||||||
"article.id, article.title, ctg.name as ctg_name",
|
|
||||||
Map{
|
|
||||||
"article.state": 0,
|
|
||||||
"LIMIT": 5,
|
|
||||||
})
|
|
||||||
test2["result"] = len(articles2) >= 0
|
|
||||||
test2["count"] = len(articles2)
|
|
||||||
test2["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 4.3 多表 JOIN - article 关联 ctg 和 admin
|
|
||||||
test3 := Map{"name": "多表 JOIN"}
|
|
||||||
articles3 := that.Db.Table("article").
|
|
||||||
LeftJoin("ctg", "article.ctg_id = ctg.id").
|
|
||||||
LeftJoin("admin", "article.admin_id = admin.id").
|
|
||||||
Where("article.state", 0).
|
|
||||||
Limit(0, 5).
|
|
||||||
Select("article.id, article.title, ctg.name as ctg_name, admin.name as admin_name")
|
|
||||||
test3["result"] = len(articles3) >= 0
|
|
||||||
test3["count"] = len(articles3)
|
|
||||||
test3["data"] = articles3
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// 4.4 INNER JOIN
|
|
||||||
test4 := Map{"name": "INNER JOIN"}
|
|
||||||
articles4 := that.Db.Table("article").
|
|
||||||
InnerJoin("ctg", "article.ctg_id = ctg.id").
|
|
||||||
Where("ctg.state", 0).
|
|
||||||
Limit(0, 5).
|
|
||||||
Select("article.id, article.title, ctg.name as ctg_name")
|
|
||||||
test4["result"] = len(articles4) >= 0
|
|
||||||
test4["count"] = len(articles4)
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 5. 聚合函数测试 ====================
|
|
||||||
func testAggregate(that *Context) Map {
|
|
||||||
result := Map{"name": "聚合函数测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 5.1 Count 总数
|
|
||||||
test1 := Map{"name": "Count 总数统计"}
|
|
||||||
count1 := that.Db.Count("article")
|
|
||||||
test1["result"] = count1 >= 0
|
|
||||||
test1["count"] = count1
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 5.2 Count 带条件
|
|
||||||
test2 := Map{"name": "Count 条件统计"}
|
|
||||||
count2 := that.Db.Count("article", Map{"state": 0})
|
|
||||||
test2["result"] = count2 >= 0
|
|
||||||
test2["count"] = count2
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 5.3 Sum 求和
|
|
||||||
test3 := Map{"name": "Sum 求和"}
|
|
||||||
sum3 := that.Db.Sum("article", "click_num", Map{"state": 0})
|
|
||||||
test3["result"] = sum3 >= 0
|
|
||||||
test3["sum"] = sum3
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// 5.4 Avg 平均值
|
|
||||||
test4 := Map{"name": "Avg 平均值"}
|
|
||||||
avg4 := that.Db.Avg("article", "click_num", Map{"state": 0})
|
|
||||||
test4["result"] = avg4 >= 0
|
|
||||||
test4["avg"] = avg4
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
// 5.5 Max 最大值
|
|
||||||
test5 := Map{"name": "Max 最大值"}
|
|
||||||
max5 := that.Db.Max("article", "click_num", Map{"state": 0})
|
|
||||||
test5["result"] = max5 >= 0
|
|
||||||
test5["max"] = max5
|
|
||||||
tests = append(tests, test5)
|
|
||||||
|
|
||||||
// 5.6 Min 最小值
|
|
||||||
test6 := Map{"name": "Min 最小值"}
|
|
||||||
min6 := that.Db.Min("article", "sort", Map{"state": 0})
|
|
||||||
test6["result"] = true // sort 可能为 0
|
|
||||||
test6["min"] = min6
|
|
||||||
tests = append(tests, test6)
|
|
||||||
|
|
||||||
// 5.7 GROUP BY 分组统计
|
|
||||||
test7 := Map{"name": "GROUP BY 分组统计"}
|
|
||||||
stats7 := that.Db.Select("article",
|
|
||||||
"ctg_id, COUNT(*) as article_count, AVG(click_num) as avg_clicks, SUM(click_num) as total_clicks",
|
|
||||||
Map{
|
|
||||||
"state": 0,
|
|
||||||
"GROUP": "ctg_id",
|
|
||||||
"ORDER": "article_count DESC",
|
|
||||||
"LIMIT": 10,
|
|
||||||
})
|
|
||||||
test7["result"] = len(stats7) >= 0
|
|
||||||
test7["stats"] = stats7
|
|
||||||
tests = append(tests, test7)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 6. 分页查询测试 ====================
|
|
||||||
func testPagination(that *Context) Map {
|
|
||||||
result := Map{"name": "分页查询测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 6.1 PageSelect 分页查询
|
|
||||||
test1 := Map{"name": "PageSelect 分页查询"}
|
|
||||||
articles1 := that.Db.Page(1, 5).PageSelect("article", "*", Map{
|
|
||||||
"state": 0,
|
|
||||||
"ORDER": "id DESC",
|
|
||||||
})
|
|
||||||
test1["result"] = len(articles1) <= 5
|
|
||||||
test1["count"] = len(articles1)
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 6.2 第二页
|
|
||||||
test2 := Map{"name": "PageSelect 第二页"}
|
|
||||||
articles2 := that.Db.Page(2, 5).PageSelect("article", "*", Map{
|
|
||||||
"state": 0,
|
|
||||||
"ORDER": "id DESC",
|
|
||||||
})
|
|
||||||
test2["result"] = len(articles2) <= 5
|
|
||||||
test2["count"] = len(articles2)
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 6.3 链式分页
|
|
||||||
test3 := Map{"name": "链式 Page 分页"}
|
|
||||||
articles3 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Order("id DESC").
|
|
||||||
Page(1, 3).
|
|
||||||
Select("id,title,author")
|
|
||||||
test3["result"] = len(articles3) <= 3
|
|
||||||
test3["count"] = len(articles3)
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// 6.4 Offset 偏移
|
|
||||||
test4 := Map{"name": "Offset 偏移查询"}
|
|
||||||
articles4 := that.Db.Table("article").
|
|
||||||
Where("state", 0).
|
|
||||||
Limit(3).
|
|
||||||
Offset(2).
|
|
||||||
Select("id,title")
|
|
||||||
test4["result"] = len(articles4) <= 3
|
|
||||||
test4["count"] = len(articles4)
|
|
||||||
test4["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 7. 批量插入测试 ====================
|
|
||||||
func testInserts(that *Context) Map {
|
|
||||||
result := Map{"name": "批量插入测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 7.1 批量插入
|
|
||||||
test1 := Map{"name": "Inserts 批量插入"}
|
|
||||||
timestamp := time.Now().UnixNano()
|
|
||||||
affected1 := that.Db.Inserts("test_batch", []Map{
|
|
||||||
{"name": fmt.Sprintf("批量测试1_%d", timestamp), "title": "标题1", "state": 1},
|
|
||||||
{"name": fmt.Sprintf("批量测试2_%d", timestamp), "title": "标题2", "state": 1},
|
|
||||||
{"name": fmt.Sprintf("批量测试3_%d", timestamp), "title": "标题3", "state": 1},
|
|
||||||
})
|
|
||||||
test1["result"] = affected1 >= 0
|
|
||||||
test1["affected"] = affected1
|
|
||||||
test1["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 7.2 带 [#] 的批量插入
|
|
||||||
test2 := Map{"name": "Inserts 带 [#] 标记"}
|
|
||||||
timestamp2 := time.Now().UnixNano()
|
|
||||||
affected2 := that.Db.Inserts("test_batch", []Map{
|
|
||||||
{"name": fmt.Sprintf("带时间测试1_%d", timestamp2), "title": "标题带时间1", "state": 1, "create_time[#]": "NOW()"},
|
|
||||||
{"name": fmt.Sprintf("带时间测试2_%d", timestamp2), "title": "标题带时间2", "state": 1, "create_time[#]": "NOW()"},
|
|
||||||
})
|
|
||||||
test2["result"] = affected2 >= 0
|
|
||||||
test2["affected"] = affected2
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp)})
|
|
||||||
that.Db.Delete("test_batch", Map{"name[~]": fmt.Sprintf("_%d", timestamp2)})
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 8. Upsert 测试 ====================
|
|
||||||
func testUpsert(that *Context) Map {
|
|
||||||
result := Map{"name": "Upsert测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 使用 admin 表测试 Upsert(MySQL ON DUPLICATE KEY UPDATE)
|
|
||||||
timestamp := time.Now().Unix()
|
|
||||||
testPhone := fmt.Sprintf("199%08d", timestamp%100000000)
|
|
||||||
|
|
||||||
// 8.1 Upsert 插入新记录
|
|
||||||
test1 := Map{"name": "Upsert 插入新记录 (admin表)"}
|
|
||||||
affected1 := that.Db.Upsert("admin",
|
|
||||||
Map{
|
|
||||||
"name": "Upsert测试管理员",
|
|
||||||
"phone": testPhone,
|
|
||||||
"state": 1,
|
|
||||||
"password": "test123",
|
|
||||||
"role_id": 1,
|
|
||||||
"title": "测试职位",
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
"modify_time[#]": "NOW()",
|
|
||||||
},
|
|
||||||
Slice{"phone"},
|
|
||||||
Slice{"name", "state", "title", "modify_time"},
|
|
||||||
)
|
|
||||||
test1["result"] = affected1 >= 0
|
|
||||||
test1["affected"] = affected1
|
|
||||||
test1["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 8.2 Upsert 更新已存在记录
|
|
||||||
test2 := Map{"name": "Upsert 更新已存在记录"}
|
|
||||||
affected2 := that.Db.Upsert("admin",
|
|
||||||
Map{
|
|
||||||
"name": "Upsert更新后管理员",
|
|
||||||
"phone": testPhone,
|
|
||||||
"state": 1,
|
|
||||||
"password": "updated123",
|
|
||||||
"role_id": 2,
|
|
||||||
"title": "更新后职位",
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
"modify_time[#]": "NOW()",
|
|
||||||
},
|
|
||||||
Slice{"phone"},
|
|
||||||
Slice{"name", "title", "role_id", "modify_time"},
|
|
||||||
)
|
|
||||||
test2["result"] = affected2 >= 0
|
|
||||||
test2["affected"] = affected2
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 验证更新结果
|
|
||||||
updatedAdmin := that.Db.Get("admin", "*", Map{"phone": testPhone})
|
|
||||||
test2["updatedAdmin"] = updatedAdmin
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
that.Db.Delete("admin", Map{"phone": testPhone})
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 9. 事务测试 ====================
|
|
||||||
func testTransaction(that *Context) Map {
|
|
||||||
result := Map{"name": "事务测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 9.1 事务成功提交
|
|
||||||
test1 := Map{"name": "事务成功提交"}
|
|
||||||
timestamp := time.Now().Unix()
|
|
||||||
testName1 := fmt.Sprintf("事务测试_%d", timestamp)
|
|
||||||
|
|
||||||
success1 := that.Db.Action(func(tx HoTimeDB) bool {
|
|
||||||
// 插入记录到 test_batch 表
|
|
||||||
recordId := tx.Insert("test_batch", Map{
|
|
||||||
"name": testName1,
|
|
||||||
"title": "事务提交测试",
|
|
||||||
"state": 1,
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
})
|
|
||||||
|
|
||||||
return recordId != 0
|
|
||||||
})
|
|
||||||
|
|
||||||
test1["result"] = success1
|
|
||||||
// 验证数据是否存在
|
|
||||||
checkRecord := that.Db.Get("test_batch", "*", Map{"name": testName1})
|
|
||||||
test1["recordExists"] = checkRecord != nil
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 9.2 事务回滚
|
|
||||||
test2 := Map{"name": "事务回滚"}
|
|
||||||
testName2 := fmt.Sprintf("事务回滚测试_%d", timestamp)
|
|
||||||
|
|
||||||
success2 := that.Db.Action(func(tx HoTimeDB) bool {
|
|
||||||
// 插入记录
|
|
||||||
_ = tx.Insert("test_batch", Map{
|
|
||||||
"name": testName2,
|
|
||||||
"title": "事务回滚测试",
|
|
||||||
"state": 1,
|
|
||||||
"create_time[#]": "NOW()",
|
|
||||||
})
|
|
||||||
|
|
||||||
// 返回 false 触发回滚
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
test2["result"] = !success2 // 期望回滚,所以 success2 应该为 false
|
|
||||||
// 验证数据是否不存在(已回滚)
|
|
||||||
checkRecord2 := that.Db.Get("test_batch", "*", Map{"name": testName2})
|
|
||||||
test2["recordRolledBack"] = checkRecord2 == nil
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
that.Db.Delete("test_batch", Map{"name": testName1})
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 10. 原生 SQL 测试 ====================
|
|
||||||
func testRawSQL(that *Context) Map {
|
|
||||||
result := Map{"name": "原生SQL测试", "tests": Slice{}}
|
|
||||||
tests := Slice{}
|
|
||||||
|
|
||||||
// 10.1 Query 查询 - 使用真实的 article 表
|
|
||||||
test1 := Map{"name": "Query 原生查询"}
|
|
||||||
articles1 := that.Db.Query("SELECT id, title, author FROM `article` WHERE state = ? LIMIT ?", 0, 5)
|
|
||||||
test1["result"] = len(articles1) >= 0
|
|
||||||
test1["count"] = len(articles1)
|
|
||||||
tests = append(tests, test1)
|
|
||||||
|
|
||||||
// 10.2 Exec 执行 - 使用 article 表
|
|
||||||
test2 := Map{"name": "Exec 原生执行"}
|
|
||||||
// 更新一条记录
|
|
||||||
testArticle := that.Db.Get("article", "id", Map{"state": 0})
|
|
||||||
if testArticle != nil {
|
|
||||||
res, err := that.Db.Exec("UPDATE `article` SET modify_time = NOW() WHERE id = ?", testArticle.GetInt64("id"))
|
|
||||||
if err.GetError() == nil && res != nil {
|
|
||||||
affected, _ := res.RowsAffected()
|
|
||||||
test2["result"] = affected >= 0
|
|
||||||
test2["affected"] = affected
|
|
||||||
} else {
|
|
||||||
test2["result"] = false
|
|
||||||
test2["error"] = err.GetError()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
test2["result"] = true
|
|
||||||
test2["note"] = "无可用测试数据"
|
|
||||||
}
|
|
||||||
tests = append(tests, test2)
|
|
||||||
|
|
||||||
// ==================== IN/NOT IN 数组测试 ====================
|
|
||||||
// H1: IN (?) 配合非空数组能正确展开
|
|
||||||
test3 := Map{"name": "H1: IN (?) 非空数组展开"}
|
|
||||||
articles3 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{1, 2, 3, 4, 5})
|
|
||||||
test3["result"] = len(articles3) >= 0
|
|
||||||
test3["count"] = len(articles3)
|
|
||||||
test3["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test3)
|
|
||||||
|
|
||||||
// H2: IN (?) 配合空数组替换为 1=0
|
|
||||||
test4 := Map{"name": "H2: IN (?) 空数组替换为1=0"}
|
|
||||||
articles4 := that.Db.Query("SELECT id, title FROM `article` WHERE id IN (?) LIMIT 10", []int{})
|
|
||||||
test4["result"] = len(articles4) == 0 // 空数组的IN应该返回0条
|
|
||||||
test4["count"] = len(articles4)
|
|
||||||
test4["lastQuery"] = that.Db.LastQuery
|
|
||||||
test4["expected"] = "count=0, SQL应包含1=0"
|
|
||||||
tests = append(tests, test4)
|
|
||||||
|
|
||||||
// H3: NOT IN (?) 配合空数组替换为 1=1
|
|
||||||
test5 := Map{"name": "H3: NOT IN (?) 空数组替换为1=1"}
|
|
||||||
articles5 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{})
|
|
||||||
test5["result"] = len(articles5) > 0 // NOT IN空数组应该返回记录
|
|
||||||
test5["count"] = len(articles5)
|
|
||||||
test5["lastQuery"] = that.Db.LastQuery
|
|
||||||
test5["expected"] = "count>0, SQL应包含1=1"
|
|
||||||
tests = append(tests, test5)
|
|
||||||
|
|
||||||
// H4: NOT IN (?) 配合非空数组正常展开
|
|
||||||
test6 := Map{"name": "H4: NOT IN (?) 非空数组展开"}
|
|
||||||
articles6 := that.Db.Query("SELECT id, title FROM `article` WHERE id NOT IN (?) LIMIT 10", []int{1, 2, 3})
|
|
||||||
test6["result"] = len(articles6) >= 0
|
|
||||||
test6["count"] = len(articles6)
|
|
||||||
test6["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test6)
|
|
||||||
|
|
||||||
// H5: 普通 ? 占位符保持原有行为
|
|
||||||
test7 := Map{"name": "H5: 普通?占位符不受影响"}
|
|
||||||
articles7 := that.Db.Query("SELECT id, title FROM `article` WHERE state = ? LIMIT ?", 0, 5)
|
|
||||||
test7["result"] = len(articles7) >= 0
|
|
||||||
test7["count"] = len(articles7)
|
|
||||||
test7["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test7)
|
|
||||||
|
|
||||||
// 额外测试: Select ORM方法的空数组处理
|
|
||||||
test8 := Map{"name": "ORM Select: IN空数组"}
|
|
||||||
articles8 := that.Db.Select("article", "id,title", Map{"id": []int{}, "LIMIT": 10})
|
|
||||||
test8["result"] = len(articles8) == 0
|
|
||||||
test8["count"] = len(articles8)
|
|
||||||
test8["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test8)
|
|
||||||
|
|
||||||
// 额外测试: Select ORM方法的NOT IN空数组处理
|
|
||||||
test9 := Map{"name": "ORM Select: NOT IN空数组"}
|
|
||||||
articles9 := that.Db.Select("article", "id,title", Map{"id[!]": []int{}, "LIMIT": 10})
|
|
||||||
test9["result"] = len(articles9) > 0 // NOT IN 空数组应返回记录
|
|
||||||
test9["count"] = len(articles9)
|
|
||||||
test9["lastQuery"] = that.Db.LastQuery
|
|
||||||
tests = append(tests, test9)
|
|
||||||
|
|
||||||
result["tests"] = tests
|
|
||||||
result["success"] = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
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}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
1
example/tpt/css/chunk-1afce11c.7ec257c2.css
Normal file
1
example/tpt/css/chunk-1afce11c.7ec257c2.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.left-nav-home-bar{background:#2c3759!important}.left-nav-home-bar,.left-nav-home-bar i{color:#fff!important}.el-submenu .el-menu-item{height:40px;line-height:40px}.el-menu .el-submenu__title{height:46px;line-height:46px}.left-nav-home-bar i{margin-bottom:6px!important}.el-menu-item-group__title{padding:0 0 0 20px}.head-left[data-v-297b6686],.head-right[data-v-297b6686]{display:flex;justify-content:center;flex-direction:column}.head-right[data-v-297b6686]{align-items:flex-end}.el-upload{height:100px;width:100px}.el-upload img[data-v-012633a9]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-012633a9]{font-size:40px;margin:30% 31%;display:block}.form-file-item .upload-file .el-upload{width:100px;height:auto;line-height:0;background-color:transparent}.form-file-item .el-button--small{height:32px}.el-input-number--mini{width:100px;margin-right:5px}.el-input-number--mini .el-input{width:100px!important}.basic-form{width:400px}.basic-form .el-input,.basic-form .el-select{width:84px;margin-right:5px}.form-file-item{margin-bottom:5px}.form-file-item .el-upload{width:60px;height:60px;line-height:60px}.form-file-item .file-item .name{width:100px}.form-file-item .name{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.form-list .form-common-item{display:flex;margin-bottom:5px}.form-common-item .name,.form-common-item .value{width:88px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list .select-list-item{display:flex;margin-bottom:5px}.select-list-item .name{width:76px}.select-list-item .name,.select-list-item .value{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list-item .value{width:77px}.select-wrap{text-align:left;margin-bottom:5px}.select-wrap .select-add-name{display:inline-block;width:87px;text-align:right}.select-wrap .el-input{width:84px}.el-input__inner{padding-left:10px;padding-right:5px}.el-upload{height:60px;width:60px;background:#eee;overflow:hidden}
|
||||||
1
example/tpt/css/chunk-1dd2a8d0.e4ca99de.css
Normal file
1
example/tpt/css/chunk-1dd2a8d0.e4ca99de.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.not-show-tab-label .el-tabs__header,.not-show-tab-search{display:none}
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
1
example/tpt/css/chunk-2f62f180.5c448e68.css
Normal file
1
example/tpt/css/chunk-2f62f180.5c448e68.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.el-upload{height:100px;width:100px}.el-upload img[data-v-012633a9]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-012633a9]{font-size:40px;margin:30% 31%;display:block}.form-file-item .upload-file .el-upload{width:100px;height:auto;line-height:0;background-color:transparent}.form-file-item .el-button--small{height:32px}.el-input-number--mini{width:100px;margin-right:5px}.el-input-number--mini .el-input{width:100px!important}.basic-form{width:400px}.basic-form .el-input,.basic-form .el-select{width:84px;margin-right:5px}.form-file-item{margin-bottom:5px}.form-file-item .el-upload{width:60px;height:60px;line-height:60px}.form-file-item .file-item .name{width:100px}.form-file-item .name{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.form-list .form-common-item{display:flex;margin-bottom:5px}.form-common-item .name,.form-common-item .value{width:88px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list .select-list-item{display:flex;margin-bottom:5px}.select-list-item .name{width:76px}.select-list-item .name,.select-list-item .value{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list-item .value{width:77px}.select-wrap{text-align:left;margin-bottom:5px}.select-wrap .select-add-name{display:inline-block;width:87px;text-align:right}.select-wrap .el-input{width:84px}.el-input__inner{padding-left:10px;padding-right:5px}.el-upload{height:60px;width:60px;background:#eee;overflow:hidden}
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.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
1
example/tpt/css/chunk-a74869b6.c460e209.css
Normal file
1
example/tpt/css/chunk-a74869b6.c460e209.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
body[data-v-581864d3],dd[data-v-581864d3],dl[data-v-581864d3],form[data-v-581864d3],h1[data-v-581864d3],h2[data-v-581864d3],h3[data-v-581864d3],h4[data-v-581864d3],h5[data-v-581864d3],h6[data-v-581864d3],html[data-v-581864d3],ol[data-v-581864d3],p[data-v-581864d3],pre[data-v-581864d3],tbody[data-v-581864d3],textarea[data-v-581864d3],tfoot[data-v-581864d3],thead[data-v-581864d3],ul[data-v-581864d3]{margin:0;font-size:14px;font-family:Microsoft YaHei}dl[data-v-581864d3],ol[data-v-581864d3],ul[data-v-581864d3]{padding:0}li[data-v-581864d3]{list-style:none}input[data-v-581864d3]{border:none;outline:none;font-family:Microsoft YaHei;background-color:#fff}a[data-v-581864d3]{font-family:Microsoft YaHei;text-decoration:none}[data-v-581864d3]{margin:0;padding:0}.login[data-v-581864d3]{position:relative;width:100%;height:100%;background-color:#353d56}.login-item[data-v-581864d3]{position:absolute;top:calc(50% - 244px);left:calc(50% - 244px);width:488px;height:488px;padding:80px 0 152px 0;box-sizing:border-box;background-size:468px 468px}.login-item .left-title[data-v-581864d3]{display:inline-block;width:236px;height:100%;border-right:2px solid #88919e;text-align:center;padding-top:68px;box-sizing:border-box}.login-item .left-title p[data-v-581864d3]{font-size:30px;line-height:50px;color:#fff}.login-item .right-content[data-v-581864d3]{display:inline-block;vertical-align:top;padding-left:42px;box-sizing:border-box}.login-item .right-content .login-title[data-v-581864d3]{font-size:16px;color:#fff}.errorMsg[data-v-581864d3]{width:100%;height:34px;line-height:34px;color:red;font-size:14px;overflow:hidden}.login-item .right-content .inputWrap[data-v-581864d3]{width:204px;height:36px;line-height:36px;color:#99a3b2;font-size:14px;border-bottom:3px solid #98a2b1;margin-bottom:10px}.login-item .right-content .inputWrap input[data-v-581864d3]{background-color:transparent;color:#99a3b2}.login-btn[data-v-581864d3]{width:88px;height:36px;text-align:center;line-height:36px;font-size:14px;color:#fff;background-color:#4f619b;border-radius:18px;margin-top:60px;cursor:pointer}
|
||||||
1
example/tpt/css/chunk-b504df5c.61d7456c.css
Normal file
1
example/tpt/css/chunk-b504df5c.61d7456c.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
.form-file-item .upload-file .el-upload{width:100px;height:auto;line-height:0;background-color:transparent}.form-file-item .el-button--small{height:32px}.el-input-number--mini{width:100px;margin-right:5px}.el-input-number--mini .el-input{width:100px!important}.basic-form{width:400px}.basic-form .el-input,.basic-form .el-select{width:84px;margin-right:5px}.form-file-item{margin-bottom:5px}.form-file-item .el-upload{width:60px;height:60px;line-height:60px}.form-file-item .file-item .name{width:100px}.form-file-item .name{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.form-list .form-common-item{display:flex;margin-bottom:5px}.form-common-item .name,.form-common-item .value{width:88px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list .select-list-item{display:flex;margin-bottom:5px}.select-list-item .name{width:76px}.select-list-item .name,.select-list-item .value{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.select-list-item .value{width:77px}.select-wrap{text-align:left;margin-bottom:5px}.select-wrap .select-add-name{display:inline-block;width:87px;text-align:right}.select-wrap .el-input{width:84px}.el-input__inner{padding-left:10px;padding-right:5px}.el-upload,.el-upload-list--picture-card .el-upload-list__item{height:60px;width:60px}.el-upload-list__item.is-success.focusing .el-icon-close-tip{display:none!important}.el-upload{height:100px;width:100px;background:#eee;overflow:hidden}.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-4b440c12]{height:100%;width:100%;-o-object-fit:cover;object-fit:cover;display:block}.el-upload i[data-v-4b440c12]{font-size:40px;margin:30% 31%;display:block}
|
||||||
1
example/tpt/css/chunk-d1a9ebe6.5cc24c46.css
Normal file
1
example/tpt/css/chunk-d1a9ebe6.5cc24c46.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
h3[data-v-28af1b93]{margin:40px 0 0}ul[data-v-28af1b93]{list-style-type:none;padding:0}li[data-v-28af1b93]{display:inline-block;margin:0 10px}a[data-v-28af1b93]{color:#42b983}
|
||||||
@ -1 +0,0 @@
|
|||||||
.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}
|
|
||||||
BIN
example/tpt/file/2021/11/13/9111bcfcaf8b8240976dc08e97658605.png
Normal file
BIN
example/tpt/file/2021/11/13/9111bcfcaf8b8240976dc08e97658605.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 297 KiB |
BIN
example/tpt/file/2021/11/13/9129a3b12a8eb56a47da20135f8106bc.png
Normal file
BIN
example/tpt/file/2021/11/13/9129a3b12a8eb56a47da20135f8106bc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
BIN
example/tpt/file/2021/12/27/a24a4bc4d8f4947de0eed6bcf9b7186c.jpg
Normal file
BIN
example/tpt/file/2021/12/27/a24a4bc4d8f4947de0eed6bcf9b7186c.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
Binary file not shown.
@ -1,3 +1,3 @@
|
|||||||
<!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{
|
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title></title><style>body{
|
||||||
margin: 0px;
|
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>
|
}</style><link href="css/chunk-2b8aef56.7087d841.css" rel="prefetch"><link href="css/chunk-38db7d04.2b6ce0ac.css" rel="prefetch"><link href="css/chunk-5c99f384.31e35517.css" rel="prefetch"><link href="css/chunk-60f282ff.83752cba.css" rel="prefetch"><link href="css/chunk-a74869b6.c460e209.css" rel="prefetch"><link href="css/chunk-d1a9ebe6.5cc24c46.css" rel="prefetch"><link href="js/chunk-28c289a1.0ed6fefe.js" rel="prefetch"><link href="js/chunk-2b8aef56.8330998b.js" rel="prefetch"><link href="js/chunk-2c065dd6.d9c3e429.js" rel="prefetch"><link href="js/chunk-38db7d04.18ee879a.js" rel="prefetch"><link href="js/chunk-58db4e7f.c298e695.js" rel="prefetch"><link href="js/chunk-5c99f384.be52d852.js" rel="prefetch"><link href="js/chunk-60f282ff.cbb91cc0.js" rel="prefetch"><link href="js/chunk-78ba61e2.520b239c.js" rel="prefetch"><link href="js/chunk-a74869b6.01e5db7b.js" rel="prefetch"><link href="js/chunk-d1a9ebe6.fba0f501.js" rel="prefetch"><link href="css/app.5e2eb449.css" rel="preload" as="style"><link href="js/app.c87636c4.js" rel="preload" as="script"><link href="css/app.5e2eb449.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but hotime doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/app.c87636c4.js"></script></body></html>
|
||||||
File diff suppressed because one or more lines are too long
42
example/tpt/js/app.3ff414bb.js
Normal file
42
example/tpt/js/app.3ff414bb.js
Normal file
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
@ -1 +0,0 @@
|
|||||||
(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")}}]);
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user