chore(logging): 更新日志重定向与捕获功能

- 在 .gitignore 中添加调试日志文件的忽略规则,避免不必要的调试信息被提交
- 修改 application.go 中的 stdout 重定向逻辑,使用 log.CaptureStream 以支持更灵活的日志捕获
- 更新 README 文档,增加对日志重定向功能的说明
This commit is contained in:
2026-07-13 07:45:51 +08:00
parent 74ae7217e1
commit de17ecbfd5
23 changed files with 1316 additions and 1836 deletions
+191 -559
View File
@@ -1,191 +1,13 @@
# HoTimeDB API 快速参考
## 条件查询语法规则
完整教程见 [HoTimeDB 使用说明](HoTimeDB_使用说明.md)。本文仅收录条件运算符与方法签名速查。
**新版本改进:**
- 多条件自动用 AND 连接,无需手动包装
- 关键字支持大小写(如 `LIMIT``limit` 都有效)
- 新增 `HAVING` 和独立 `OFFSET` 支持
---
```go
// ✅ 推荐:简化语法(多条件自动 AND)
Map{"status": 1, "age[>]": 18}
// 生成: WHERE `status`=? AND `age`>?
## 条件运算符
// ✅ 仍然支持:显式 AND 包装(向后兼容)
Map{
"AND": Map{
"status": 1,
"age[>]": 18,
},
}
多条件默认 AND;关键字大小写均可(`ORDER`/`order`)。
// ✅ 混合条件和特殊关键字
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"},
})
// 返回受影响的行数
// 推荐:使用服务器时间(Time2Str),避免数据库时区差异
now := Time2Str(time.Now())
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 = ?` | 等于 |
@@ -194,389 +16,199 @@ data := database.Page(page, pageSize).PageSelect("table", "fields", whereMap)
| `"field[>=]": value` | `field >= ?` | 大于等于 |
| `"field[<]": value` | `field < ?` | 小于 |
| `"field[<=]": value` | `field <= ?` | 小于等于 |
| `"field[~]": "kw"` | `LIKE '%kw%'` | 包含 |
| `"field[~!]": "kw"` | `LIKE 'kw%'` | 开头 |
| `"field[!~]": "kw"` | `LIKE '%kw'` | 结尾 |
| `"field[~~]": "%kw%"` | `LIKE '%kw%'` | 手动 LIKE |
| `"field[<>]": [min,max]` | `BETWEEN ? AND ?` | 区间内 |
| `"field[><]": [min,max]` | `NOT BETWEEN` | 区间外 |
| `"field": [v1,v2]` | `IN (?,?)` | 集合 |
| `"field[!]": [v1,v2]` | `NOT IN` | 非集合 |
| `"field": nil` | `IS NULL` | 空 |
| `"field[!]": nil` | `IS NOT NULL` | 非空 |
| `"field[#]": "balance+1"` | `field = balance+1` | 直接表达式 |
| `"[##]": "a > b"` | `a > b` | 直接 SQL 片段 |
| `"field[#!]": "1"` | `field != 1` | 非参数化不等 |
### 模糊查询
| 写法 | SQL | 说明 |
|------|-----|------|
| `"field[~]": "keyword"` | `field LIKE '%keyword%'` | 包含 |
| `"field[~!]": "keyword"` | `field LIKE 'keyword%'` | 以...开头 |
| `"field[!~]": "keyword"` | `field LIKE '%keyword'` | 以...结尾 |
| `"field[~~]": "%keyword%"` | `field LIKE '%keyword%'` | 手动LIKE |
时间字段推荐 Go 侧传值:`"create_time": Time2Str(time.Now())`(勿用 `NOW()`)。
### 范围查询
| 写法 | 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[#]": "balance + 1"` | `field = balance + 1` | 直接SQL表达式(数值运算等) |
| `"[##]": "a > b"` | `a > b` | 直接SQL片段 |
| `"field[#!]": "1"` | `field != 1` | 不等于(不参数化) |
> **时间字段推荐写法**:不要用 `"[#]": "NOW()"`,改用 Go 侧传值:
> ```go
> // 赋值当前时间
> "create_time": Time2Str(time.Now())
>
> // 时间范围查询(一天前)
> "create_time[>]": Time2Str(time.Now().AddDate(0, 0, -1))
> ```
> 原因:`NOW()` 使用数据库时区;`Time2Str(time.Now())` 使用应用服务器时区,行为稳定且跨数据库兼容。
## 逻辑连接符
### 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
```
## 达梦数据库(DM8)支持
### 连接配置
```go
import (
_ "gitee.com/chunanyong/dm" // vendor 已内置,无需额外安装
)
database := &db.HoTimeDB{
Type: "dm", // 或 "dameng"
}
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
// schema= 指定当前会话默认搜索 schema
dsn := "dm://SYSDBA:password@127.0.0.1:5236?schema=TEST"
master, _ = sql.Open("dm", dsn)
return master, master
})
```
### 各数据库差异对比
| 特性 | MySQL | PostgreSQL | 达梦 DM8 |
|------|-------|------------|---------|
| 标识符引号 | \`name\` | "name" | "name" |
| 占位符 | ? | $1, $2... | ? |
| Upsert | ON DUPLICATE KEY UPDATE | ON CONFLICT DO UPDATE | MERGE INTO...USING |
| 自增列 | AUTO_INCREMENT | SERIAL | IDENTITY(1,1) |
| 分页 | LIMIT m, n | LIMIT n OFFSET m | LIMIT m, n 或 LIMIT n OFFSET m |
框架自动处理所有差异,业务代码无需修改。
### 达梦注意事项速查
| 场景 | 说明 |
|------|------|
| 标识符大小写 | 双引号内大小写敏感,框架统一使用小写 |
| 保留字 | `admin`/`user`/`order` 等框架自动加双引号,原生SQL需手动处理 |
| 表存在检测 | 用 `COUNT(*)` 代替 `USER_TABLES`schema 错配问题) |
| `Insert` 返回ID | 原生驱动已支持 `LastInsertId()`,与 MySQL 一致 |
| 长文本类型 | DM 用 `CLOB`(对应 MySQL 的 `LONGTEXT` |
| 时间赋值 | **推荐统一用** `Time2Str(time.Now())` 传服务器时间,避免 `NOW()` 时区差异及跨库函数不兼容 |
### 原生 SQL 中的保留字
```go
// 达梦中 admin、user 等是保留字,原生 SQL 必须加双引号
results := database.Query(
`SELECT "id", "name" FROM "admin" WHERE "state" = ?`, 1)
// ORM 方法无需处理,框架自动加引号
results := database.Select("admin", "*", common.Map{"state": 1})
```
## 错误处理
```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
`)
Map{"status": 1, "age[>]": 18} // 自动 AND
Map{"AND": Map{...}} / Map{"OR": Map{...}} // 显式逻辑
Map{"AND": Map{"status": 1, "OR": Map{...}}} // 嵌套
Map{"ORDER": "id DESC"} / []string{"a DESC","b"} // ORDER BY
Map{"GROUP": "dept"} / []string{"a","b"} // GROUP BY
Map{"HAVING": Map{"COUNT(*).[>]": 5}} // HAVING
Map{"LIMIT": 20} / []int{10, 20} // LIMIT / offset+limit
Map{"OFFSET": 20} // 独立 OFFSET
```
---
*快速参考版本: 2.1*
*更新日期: 2026年3月*
## 方法签名
**详细说明:**
- [HoTimeDB 使用说明](HoTimeDB_使用说明.md) - 完整教程
### 连接与元信息
| 方法 | 签名 | 说明 |
|------|------|------|
| SetConnect | `SetConnect(func() (master, slave *sql.DB))` | 设置连接并 InitDb |
| InitDb | `InitDb()` | 初始化连接与方言 |
| GetPrefix | `GetPrefix() string` | 表前缀 |
| GetType | `GetType() string` | 库类型 |
| GetDialect | `GetDialect() Dialect` | 方言适配器 |
| GetLast | `GetLast() *DBError` | 最近一次 SQL 快照 |
| GetLastError | `GetLastError() error` | 最近错误 |
| GetLastQuery | `GetLastQuery() string` | 最近 SQL |
| GetLastData | `GetLastData() []interface{}` | 最近参数 |
`Type``mysql` / `sqlite3` / `postgres` / `dm`(或 `dameng`)。
### CRUD
| 方法 | 签名 | 返回 |
|------|------|------|
| Select | `Select(table string, qu ...interface{})` | `[]Map` |
| Get | `Get(table string, qu ...interface{})` | `Map`(自动 LIMIT 1 |
| Insert | `Insert(table string, data map[string]interface{})` | `int64` 新 ID |
| Inserts | `Inserts(table string, dataList []Map)` | `int64` 影响行数 |
| Update | `Update(table string, data Map, where Map)` | `int64` |
| Upsert | `Upsert(table string, data Map, uniqueKeys Slice, updateColumns ...interface{})` | `int64` |
| Delete | `Delete(table string, data map[string]interface{})` | `int64` |
| Page | `Page(page, pageRow int) *HoTimeDB` | 链式分页 |
| PageSelect | `PageSelect(table string, qu ...interface{})` | `[]Map` |
`Select`/`Get`/`PageSelect``qu` 常见形态:字段;字段+wherejoin+字段+where。
```go
database.Select("user")
database.Select("user", "id,name")
database.Select("user", "*", where)
database.Select("user", joinSlice, "fields", where)
database.Get("user", "fields", where)
database.Insert("user", Map{"name": "a"})
database.Inserts("user", []Map{{"name": "a"}, {"name": "b"}})
database.Update("user", Map{"name": "b"}, Map{"id": 1})
database.Upsert("user", data, Slice{"id"}, Slice{"name", "email"})
database.Upsert("user", data, Slice{"id"}, "name", "email")
database.Delete("user", Map{"id": 1})
database.Page(1, 20).PageSelect("user", "id,name", where)
```
### 聚合
| 方法 | 签名 | 返回 |
|------|------|------|
| Count | `Count(table string, qu ...interface{})` | `int` |
| Sum | `Sum(table string, column string, qu ...interface{})` | `float64` |
| Avg | `Avg(table string, column string, qu ...interface{})` | `float64` |
| Max | `Max(table string, column string, qu ...interface{})` | `float64` |
| Min | `Min(table string, column string, qu ...interface{})` | `float64` |
`qu` 可为 where,或 join+where。
```go
database.Count("user")
database.Count("user", where)
database.Count("user", join, where)
database.Sum("order", "amount", where)
database.Avg("order", "amount", where)
database.Max("order", "amount", where)
database.Min("order", "amount", where)
```
### 链式构建器
```go
b := database.Table("tablename") // *HotimeDBBuilder
```
| 方法 | 签名 | 说明 |
|------|------|------|
| Where / And / Or | `(qu ...interface{}) *HotimeDBBuilder` | 键值或 Map |
| LeftJoin / RightJoin / InnerJoin / FullJoin | `(table, condition string)` | JOIN |
| Join | `(qu ...interface{})` | 通用 JOIN Map |
| Order / Group / Limit | `(qu ...interface{})` | 排序/分组/限制 |
| Having | `(qu ...interface{})` | HAVING |
| Page | `(page, pageRow int)` | 分页 |
| Offset | `(offset int)` | 偏移 |
| From | `(table string)` | 换表 |
| Select | `(qu ...interface{}) []Map` | 查询 |
| Get | `(qu ...interface{}) Map` | 单条 |
| Count | `() int` | 计数 |
| Update | `(qu ...interface{}) int64` | 更新 |
| Delete | `() int64` | 删除 |
```go
database.Table("user").Where("status", 1).Order("id DESC").Page(1, 20).Select("id,name")
database.Table("order").LeftJoin("user", "order.user_id = user.id").Where("order.status", "paid").Select("order.*, user.name")
```
### JOIN Map 写法
| 写法 | 类型 |
|------|------|
| `Map{"[>]profile": "user.id = profile.user_id"}` | LEFT |
| `Map{"[<]department": "user.dept_id = department.id"}` | RIGHT |
| `Map{"[><]role": "user.role_id = role.id"}` | INNER |
| `Map{"[<>]group": "user.group_id = group.id"}` | FULL |
### 事务与原生 SQL
| 方法 | 签名 | 说明 |
|------|------|------|
| Action | `Action(func(db HoTimeDB) bool) bool` | `true` 提交 / `false` 回滚 |
| Query | `Query(query string, args ...interface{}) []Map` | 原生查询 |
| Exec | `Exec(query string, args ...interface{}) (sql.Result, error)` | 原生执行 |
```go
ok := database.Action(func(tx HoTimeDB) bool {
if tx.Insert("a", data) == 0 { return false }
return true
})
database.Query("SELECT * FROM user WHERE age > ?", 18)
database.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100)
```
---
## 方言差异速查
| 特性 | MySQL | PostgreSQL | 达梦 DM8 |
|------|-------|------------|---------|
| 标识符引号 | \`name\` | `"name"` | `"name"` |
| 占位符 | `?` | `$1,$2...` | `?` |
| Upsert | ON DUPLICATE KEY | ON CONFLICT | MERGE INTO |
| 自增 | AUTO_INCREMENT | SERIAL | IDENTITY(1,1) |
| 分页 | LIMIT m,n | LIMIT n OFFSET m | 两者皆可 |
框架自动适配,业务代码通常无需按库分支。达梦:保留字(`admin`/`user`/`order`)ORM 自动加引号,原生 SQL 需手动双引号;时间统一用 `Time2Str(time.Now())`
```go
// Type: "postgres" | "dm" | "dameng" | "mysql" | "sqlite3"
database := &db.HoTimeDB{Type: "dm"}
database.SetConnect(func() (master, slave *sql.DB) {
master, _ = sql.Open("dm", "dm://SYSDBA:pwd@127.0.0.1:5236?schema=TEST")
return master, master
})
```
---
## 常用一行模式
```go
// 分页列表
total := database.Count("user", Map{"status": 1})
rows := database.Table("user").Where("status", 1).Order("id DESC").Page(page, size).Select("id,name")
// 分组统计
database.Select("order", "user_id, COUNT(*) c, SUM(amount) s", Map{
"status": "paid", "GROUP": "user_id", "ORDER": "s DESC",
})
// 查错
if err := database.GetLastError(); err != nil { /* ... */ }
_ = database.GetLastQuery(); _ = database.GetLastData()
```