diff --git a/.cursor/plans/集成请求参数获取方法_60cbd8aa.plan.md b/.cursor/plans/集成请求参数获取方法_60cbd8aa.plan.md index a7523f4..f3f0ca9 100644 --- a/.cursor/plans/集成请求参数获取方法_60cbd8aa.plan.md +++ b/.cursor/plans/集成请求参数获取方法_60cbd8aa.plan.md @@ -7,27 +7,27 @@ todos: status: completed - id: impl-params content: 实现 ReqParam/ReqParams 方法(获取 URL 参数,返回 *Obj) - status: in_progress + status: completed dependencies: - add-imports - id: impl-form content: 实现 ReqForm/ReqForms 方法(获取表单数据,返回 *Obj) - status: pending + status: completed dependencies: - add-imports - id: impl-json content: 实现 ReqJson/ReqJsons 方法(获取 JSON Body,返回 *Obj) - status: pending + status: completed dependencies: - add-imports - id: impl-file content: 实现 ReqFile/ReqFiles 方法(获取上传文件) - status: pending + status: completed dependencies: - add-imports - id: impl-reqdata content: 实现 ReqData/ReqDatas 方法(统一获取,返回 *Obj) - status: pending + status: completed dependencies: - impl-params - impl-form diff --git a/.gitignore b/.gitignore index c40d862..2e35775 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .idea /example/config/app.json /example/tpt/demo/ +/example/config/ +/*.exe diff --git a/context.go b/context.go index 19f4953..b14c816 100644 --- a/context.go +++ b/context.go @@ -1,12 +1,17 @@ package hotime import ( - . "code.hoteas.com/golang/hotime/common" - . "code.hoteas.com/golang/hotime/db" + "bytes" "encoding/json" + "io" + "mime/multipart" "net/http" "strings" + "sync" "time" + + . "code.hoteas.com/golang/hotime/common" + . "code.hoteas.com/golang/hotime/db" ) type Context struct { @@ -23,6 +28,10 @@ type Context struct { SessionIns DataSize int HandlerStr string //复写请求url + + // 请求参数缓存 + reqJsonCache Map // JSON Body 缓存 + reqMu sync.Once // 确保 JSON 只解析一次 } // Mtd 唯一标志 @@ -100,5 +109,97 @@ func (that *Context) View() { that.DataSize = len(d) that.RespData = nil that.Resp.Write(d) - return +} + +// ==================== 请求参数获取方法 ==================== + +// ReqParam 获取 URL 查询参数,返回 *Obj 支持链式调用 +// 用法: that.ReqParam("id").ToInt() +func (that *Context) ReqParam(key string) *Obj { + v := that.Req.URL.Query().Get(key) + if v == "" { + return &Obj{Data: nil} + } + return &Obj{Data: v} +} + +// ReqForm 获取表单参数,返回 *Obj 支持链式调用 +// 用法: that.ReqForm("name").ToStr() +func (that *Context) ReqForm(key string) *Obj { + v := that.Req.FormValue(key) + if v == "" { + return &Obj{Data: nil} + } + return &Obj{Data: v} +} + +// ReqJson 获取 JSON Body 中的字段,返回 *Obj 支持链式调用 +// 用法: that.ReqJson("data").ToMap() +func (that *Context) ReqJson(key string) *Obj { + that.parseJsonBody() + if that.reqJsonCache == nil { + return &Obj{Data: nil} + } + return &Obj{Data: that.reqJsonCache.Get(key)} +} + +// parseJsonBody 解析 JSON Body(只解析一次,并发安全) +func (that *Context) parseJsonBody() { + that.reqMu.Do(func() { + if that.Req.Body == nil { + return + } + body, err := io.ReadAll(that.Req.Body) + if err != nil || len(body) == 0 { + return + } + // 恢复 Body 以便后续代码可以再次读取 + that.Req.Body = io.NopCloser(bytes.NewBuffer(body)) + that.reqJsonCache = ObjToMap(string(body)) + }) +} + +// ReqData 统一获取请求参数,优先级: JSON > Form > URL +// 用法: that.ReqData("id").ToInt() +func (that *Context) ReqData(key string) *Obj { + // 1. 优先从 JSON Body 获取 + that.parseJsonBody() + if that.reqJsonCache != nil { + if v := that.reqJsonCache.Get(key); v != nil { + return &Obj{Data: v} + } + } + // 2. 其次从 Form 获取 + if v := that.Req.FormValue(key); v != "" { + return &Obj{Data: v} + } + // 3. 最后从 URL 参数获取 + if v := that.Req.URL.Query().Get(key); v != "" { + return &Obj{Data: v} + } + return &Obj{Data: nil} +} + +// ReqFile 获取单个上传文件 +// 用法: file, header, err := that.ReqFile("avatar") +func (that *Context) ReqFile(name string) (multipart.File, *multipart.FileHeader, error) { + return that.Req.FormFile(name) +} + +// ReqFiles 获取多个同名上传文件(批量上传场景) +// 用法: files, err := that.ReqFiles("images") +func (that *Context) ReqFiles(name string) ([]*multipart.FileHeader, error) { + if that.Req.MultipartForm == nil { + if err := that.Req.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + } + if that.Req.MultipartForm == nil || that.Req.MultipartForm.File == nil { + return nil, http.ErrMissingFile + } + files, ok := that.Req.MultipartForm.File[name] + if !ok { + return nil, http.ErrMissingFile + } + return files, nil } diff --git a/db/HoTimeDB_API参考.md b/db/HoTimeDB_API参考.md index 37c12c3..1310e85 100644 --- a/db/HoTimeDB_API参考.md +++ b/db/HoTimeDB_API参考.md @@ -1,17 +1,18 @@ # HoTimeDB API 快速参考 -## ⚠️ 重要语法说明 +## 条件查询语法规则 -**条件查询语法规则:** -- 单个条件:可以直接写在Map中 -- 多个条件:必须使用`AND`或`OR`包装 -- 特殊条件:`ORDER`、`GROUP`、`LIMIT`与条件同级 +**新版本改进:** +- 多条件自动用 AND 连接,无需手动包装 +- 关键字支持大小写(如 `LIMIT` 和 `limit` 都有效) +- 新增 `HAVING` 和独立 `OFFSET` 支持 ```go -// ✅ 正确:单个条件 -Map{"status": 1} +// ✅ 推荐:简化语法(多条件自动 AND) +Map{"status": 1, "age[>]": 18} +// 生成: WHERE `status`=? AND `age`>? -// ✅ 正确:多个条件用AND包装 +// ✅ 仍然支持:显式 AND 包装(向后兼容) Map{ "AND": Map{ "status": 1, @@ -19,20 +20,12 @@ Map{ }, } -// ✅ 正确:条件 + 特殊参数 -Map{ - "AND": Map{ - "status": 1, - "age[>]": 18, - }, - "ORDER": "id DESC", - "LIMIT": 10, -} - -// ❌ 错误:多个条件不用AND包装 +// ✅ 混合条件和特殊关键字 Map{ "status": 1, - "age[>]": 18, // 这样写不支持! + "age[>]": 18, + "ORDER": "id DESC", // 或 "order": "id DESC" + "LIMIT": 10, // 或 "limit": 10 } ``` @@ -40,14 +33,14 @@ Map{ ### 数据库连接 ```go -db.SetConnect(func() (master, slave *sql.DB) { ... }) -db.InitDb() +database.SetConnect(func() (master, slave *sql.DB) { ... }) +database.InitDb() ``` ### 链式查询构建器 ```go // 创建查询构建器 -builder := db.Table("tablename") +builder := database.Table("tablename") // 设置条件 builder.Where(key, value) @@ -65,9 +58,11 @@ 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 @@ -82,36 +77,66 @@ builder.Delete() // 返回 int64 ### 查询 (Select) ```go // 基本查询 -data := db.Select("table") -data := db.Select("table", "field1,field2") -data := db.Select("table", []string{"field1", "field2"}) -data := db.Select("table", "*", whereMap) +data := database.Select("table") +data := database.Select("table", "field1,field2") +data := database.Select("table", []string{"field1", "field2"}) +data := database.Select("table", "*", whereMap) // 带JOIN查询 -data := db.Select("table", joinSlice, "fields", whereMap) +data := database.Select("table", joinSlice, "fields", whereMap) ``` ### 获取单条 (Get) ```go // 自动添加 LIMIT 1 -row := db.Get("table", "fields", whereMap) +row := database.Get("table", "fields", whereMap) ``` ### 插入 (Insert) ```go -id := db.Insert("table", dataMap) +id := database.Insert("table", dataMap) // 返回新插入记录的ID ``` +### 批量插入 (BatchInsert) - 新增 +```go +// 使用 []Map 格式,更直观简洁 +affected := database.BatchInsert("table", []Map{ + {"col1": "val1", "col2": "val2", "col3": "val3"}, + {"col1": "val4", "col2": "val5", "col3": "val6"}, +}) +// 返回受影响的行数 + +// 支持 [#] 标记直接 SQL +affected := database.BatchInsert("log", []Map{ + {"user_id": 1, "created_time[#]": "NOW()"}, + {"user_id": 2, "created_time[#]": "NOW()"}, +}) +``` + ### 更新 (Update) ```go -affected := db.Update("table", dataMap, whereMap) +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 := db.Delete("table", whereMap) +affected := database.Delete("table", whereMap) // 返回删除的行数 ``` @@ -119,25 +144,42 @@ affected := db.Delete("table", whereMap) ### 计数 ```go -count := db.Count("table") -count := db.Count("table", whereMap) -count := db.Count("table", joinSlice, whereMap) +count := database.Count("table") +count := database.Count("table", whereMap) +count := database.Count("table", joinSlice, whereMap) ``` ### 求和 ```go -sum := db.Sum("table", "column") -sum := db.Sum("table", "column", whereMap) -sum := db.Sum("table", "column", joinSlice, whereMap) +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 // 设置分页 -db.Page(page, pageSize) +database.Page(page, pageSize) // 分页查询 -data := db.Page(page, pageSize).PageSelect("table", "fields", whereMap) +data := database.Page(page, pageSize).PageSelect("table", "fields", whereMap) ``` ## 条件语法参考 @@ -189,6 +231,14 @@ data := db.Page(page, pageSize).PageSelect("table", "fields", whereMap) ### AND 条件 ```go +// 简化语法(推荐) +whereMap := Map{ + "status": 1, + "age[>]": 18, +} +// 生成: WHERE `status`=? AND `age`>? + +// 显式 AND(向后兼容) whereMap := Map{ "AND": Map{ "status": 1, @@ -249,7 +299,7 @@ Map{ } // 或 Map{ - "ORDER": "created_time DESC", + "order": "created_time DESC", // 支持小写 } ``` @@ -260,7 +310,17 @@ Map{ } // 或 Map{ - "GROUP": "department", + "group": "department", // 支持小写 +} +``` + +### HAVING - 新增 +```go +Map{ + "GROUP": "dept_id", + "HAVING": Map{ + "COUNT(*).[>]": 5, + }, } ``` @@ -271,13 +331,21 @@ Map{ } // 或 Map{ - "LIMIT": 20, // limit 20 + "limit": 20, // limit 20,支持小写 +} +``` + +### OFFSET - 新增 +```go +Map{ + "LIMIT": 10, + "OFFSET": 20, // 独立的 OFFSET } ``` ## 事务处理 ```go -success := db.Action(func(tx HoTimeDB) bool { +success := database.Action(func(tx HoTimeDB) bool { // 在这里执行数据库操作 // 返回 true 提交事务 // 返回 false 回滚事务 @@ -300,40 +368,55 @@ success := db.Action(func(tx HoTimeDB) bool { ### 查询 ```go -results := db.Query("SELECT * FROM user WHERE age > ?", 18) +results := database.Query("SELECT * FROM user WHERE age > ?", 18) ``` ### 执行 ```go -result, err := db.Exec("UPDATE user SET status = ? WHERE id = ?", 1, 100) +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 db.LastErr.GetError() != nil { - fmt.Println("错误:", db.LastErr.GetError()) +if database.LastErr.GetError() != nil { + fmt.Println("错误:", database.LastErr.GetError()) } // 查看最后执行的SQL -fmt.Println("SQL:", db.LastQuery) -fmt.Println("参数:", db.LastData) +fmt.Println("SQL:", database.LastQuery) +fmt.Println("参数:", database.LastData) ``` ## 工具方法 ### 数据库信息 ```go -prefix := db.GetPrefix() // 获取表前缀 -dbType := db.GetType() // 获取数据库类型 +prefix := database.GetPrefix() // 获取表前缀 +dbType := database.GetType() // 获取数据库类型 +dialect := database.GetDialect() // 获取方言适配器 ``` ### 设置模式 ```go -db.Mode = 0 // 生产模式 -db.Mode = 1 // 测试模式 -db.Mode = 2 // 开发模式(输出SQL日志) +database.Mode = 0 // 生产模式 +database.Mode = 1 // 测试模式 +database.Mode = 2 // 开发模式(输出SQL日志) ``` ## 常用查询模式 @@ -341,10 +424,10 @@ db.Mode = 2 // 开发模式(输出SQL日志) ### 分页列表查询 ```go // 获取总数 -total := db.Count("user", Map{"status": 1}) +total := database.Count("user", Map{"status": 1}) // 分页数据 -users := db.Table("user"). +users := database.Table("user"). Where("status", 1). Order("created_time DESC"). Page(page, pageSize). @@ -356,7 +439,7 @@ totalPages := (total + pageSize - 1) / pageSize ### 关联查询 ```go -orders := db.Table("order"). +orders := database.Table("order"). LeftJoin("user", "order.user_id = user.id"). LeftJoin("product", "order.product_id = product.id"). Where("order.status", "paid"). @@ -369,41 +452,38 @@ orders := db.Table("order"). ### 统计查询 ```go -stats := db.Select("order", +stats := database.Select("order", "user_id, COUNT(*) as order_count, SUM(amount) as total_amount", Map{ - "AND": Map{ - "status": "paid", - "created_time[>]": "2023-01-01", - }, + "status": "paid", + "created_time[>]": "2023-01-01", "GROUP": "user_id", "ORDER": "total_amount DESC", }) ``` -### 条件组合查询 +### 批量操作 ```go -products := db.Table("product"). - Where("status", 1). - And(Map{ - "OR": Map{ - "category_id": []int{1, 2, 3}, - "tags[~]": "热销", - }, - }). - And(Map{ - "price[<>]": []float64{10.0, 1000.0}, - }). - Order("sort DESC", "created_time DESC"). - Limit(0, 20). - Select() +// 批量插入(使用 []Map 格式) +affected := database.BatchInsert("user", []Map{ + {"name": "用户1", "email": "user1@example.com", "status": 1}, + {"name": "用户2", "email": "user2@example.com", "status": 1}, + {"name": "用户3", "email": "user3@example.com", "status": 1}, +}) + +// Upsert(插入或更新,使用 Slice 格式) +affected := database.Upsert("user", + Map{"id": 1, "name": "新名称", "email": "new@example.com"}, + Slice{"id"}, + Slice{"name", "email"}, +) ``` ## 链式调用完整示例 ```go // 复杂查询链式调用 -result := db.Table("order"). +result := database.Table("order"). LeftJoin("user", "order.user_id = user.id"). LeftJoin("product", "order.product_id = product.id"). Where("order.status", "paid"). @@ -415,6 +495,7 @@ result := db.Table("order"). }, }). Group("user.id"). + Having(Map{"total_amount[>]": 500}). Order("total_amount DESC"). Page(1, 20). Select(` @@ -428,4 +509,5 @@ result := db.Table("order"). --- -*快速参考版本: 1.0* \ No newline at end of file +*快速参考版本: 2.0* +*更新日期: 2026年1月* diff --git a/db/HoTimeDB_使用说明.md b/db/HoTimeDB_使用说明.md index 7e7e831..e7e4f49 100644 --- a/db/HoTimeDB_使用说明.md +++ b/db/HoTimeDB_使用说明.md @@ -2,7 +2,7 @@ ## 概述 -HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite等数据库,并集成了缓存、事务、链式查询等功能。 +HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL等数据库,并集成了缓存、事务、链式查询等功能。 ## 目录 @@ -12,7 +12,9 @@ HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计 - [查询(Select)](#查询select) - [获取单条记录(Get)](#获取单条记录get) - [插入(Insert)](#插入insert) + - [批量插入(BatchInsert)](#批量插入batchinsert) - [更新(Update)](#更新update) + - [Upsert操作](#upsert操作) - [删除(Delete)](#删除delete) - [链式查询构建器](#链式查询构建器) - [条件查询语法](#条件查询语法) @@ -21,6 +23,7 @@ HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计 - [聚合函数](#聚合函数) - [事务处理](#事务处理) - [缓存机制](#缓存机制) +- [PostgreSQL支持](#postgresql支持) - [高级特性](#高级特性) ## 快速开始 @@ -44,8 +47,10 @@ func createConnection() (master, slave *sql.DB) { } // 初始化HoTimeDB -db := &db.HoTimeDB{} -db.SetConnect(createConnection) +database := &db.HoTimeDB{ + Type: "mysql", // 可选:mysql, sqlite3, postgres +} +database.SetConnect(createConnection) ``` ## 数据库配置 @@ -59,7 +64,7 @@ type HoTimeDB struct { DBName string *cache.HoTimeCache Log *logrus.Logger - Type string // 数据库类型 + Type string // 数据库类型:mysql, sqlite3, postgres Prefix string // 表前缀 LastQuery string // 最后执行的SQL LastData []interface{} // 最后的参数 @@ -69,19 +74,20 @@ type HoTimeDB struct { *sql.Tx // 事务对象 SlaveDB *sql.DB // 从数据库 Mode int // 0生产模式,1测试模式,2开发模式 + Dialect Dialect // 数据库方言适配器 } ``` ### 设置表前缀 ```go -db.Prefix = "app_" +database.Prefix = "app_" ``` ### 设置运行模式 ```go -db.Mode = 2 // 开发模式,会输出SQL日志 +database.Mode = 2 // 开发模式,会输出SQL日志 ``` ## 基本操作 @@ -92,35 +98,40 @@ db.Mode = 2 // 开发模式,会输出SQL日志 ```go // 查询所有字段 -users := db.Select("user") +users := database.Select("user") // 查询指定字段 -users := db.Select("user", "id,name,email") +users := database.Select("user", "id,name,email") // 查询指定字段(数组形式) -users := db.Select("user", []string{"id", "name", "email"}) +users := database.Select("user", []string{"id", "name", "email"}) // 单条件查询 -users := db.Select("user", "*", common.Map{ +users := database.Select("user", "*", common.Map{ "status": 1, }) -// 多条件查询(必须使用AND包装) -users = db.Select("user", "*", common.Map{ - "AND": common.Map{ - "status": 1, - "age[>]": 18, - }, +// 多条件查询(自动用 AND 连接) +users := database.Select("user", "*", common.Map{ + "status": 1, + "age[>]": 18, }) +// 生成: WHERE `status`=? AND `age`>? ``` #### 复杂条件查询 -**重要说明:多个条件必须使用AND或OR包装,不能直接在根Map中写多个字段条件** - ```go -// AND条件(多个条件必须用AND包装) -users := db.Select("user", "*", common.Map{ +// 简化语法:多条件自动用 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, @@ -128,16 +139,16 @@ users := db.Select("user", "*", common.Map{ }, }) -// OR条件 -users := db.Select("user", "*", common.Map{ +// OR 条件 +users := database.Select("user", "*", common.Map{ "OR": common.Map{ "status": 1, "type": 2, }, }) -// 混合条件(嵌套AND/OR) -users := db.Select("user", "*", common.Map{ +// 混合条件(嵌套 AND/OR) +users := database.Select("user", "*", common.Map{ "AND": common.Map{ "status": 1, "OR": common.Map{ @@ -147,18 +158,16 @@ users := db.Select("user", "*", common.Map{ }, }) -// 带ORDER BY、LIMIT等特殊条件 -users := db.Select("user", "*", common.Map{ - "AND": common.Map{ - "status": 1, - "age[>]": 18, - }, - "ORDER": "id DESC", - "LIMIT": 10, +// 带 ORDER BY、LIMIT 等特殊条件(关键字支持大小写) +users := database.Select("user", "*", common.Map{ + "status": 1, + "age[>]": 18, + "ORDER": "id DESC", // 或 "order": "id DESC" + "LIMIT": 10, // 或 "limit": 10 }) // 带多个特殊条件 -users := db.Select("user", "*", common.Map{ +users := database.Select("user", "*", common.Map{ "OR": common.Map{ "level": "vip", "balance[>]": 1000, @@ -167,18 +176,33 @@ users := db.Select("user", "*", common.Map{ "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 := db.Get("user", "*", common.Map{ +user := database.Get("user", "*", common.Map{ "id": 1, }) // 获取指定字段 -user := db.Get("user", "id,name,email", common.Map{ +user := database.Get("user", "id,name,email", common.Map{ "status": 1, }) ``` @@ -187,7 +211,7 @@ user := db.Get("user", "id,name,email", common.Map{ ```go // 基本插入 -id := db.Insert("user", common.Map{ +id := database.Insert("user", common.Map{ "name": "张三", "email": "zhangsan@example.com", "age": 25, @@ -199,11 +223,31 @@ id := db.Insert("user", common.Map{ fmt.Println("插入的用户ID:", id) ``` +### 批量插入(BatchInsert) + +```go +// 批量插入多条记录(使用 []Map 格式,更直观) +affected := database.BatchInsert("user", []common.Map{ + {"name": "张三", "email": "zhang@example.com", "age": 25}, + {"name": "李四", "email": "li@example.com", "age": 30}, + {"name": "王五", "email": "wang@example.com", "age": 28}, +}) +// 生成: INSERT INTO `user` (`age`, `email`, `name`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) + +fmt.Printf("批量插入 %d 条记录\n", affected) + +// 支持 [#] 标记直接插入 SQL 表达式 +affected := database.BatchInsert("log", []common.Map{ + {"user_id": 1, "action": "login", "created_time[#]": "NOW()"}, + {"user_id": 2, "action": "logout", "created_time[#]": "NOW()"}, +}) +``` + ### 更新(Update) ```go // 基本更新 -affected := db.Update("user", common.Map{ +affected := database.Update("user", common.Map{ "name": "李四", "email": "lisi@example.com", "updated_time[#]": "NOW()", @@ -211,8 +255,8 @@ affected := db.Update("user", common.Map{ "id": 1, }) -// 条件更新 -affected := db.Update("user", common.Map{ +// 条件更新(多条件自动 AND 连接) +affected := database.Update("user", common.Map{ "status": 0, }, common.Map{ "age[<]": 18, @@ -222,16 +266,60 @@ affected := db.Update("user", common.Map{ 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 := db.Delete("user", common.Map{ +affected := database.Delete("user", common.Map{ "id": 1, }) -// 条件删除 -affected := db.Delete("user", common.Map{ +// 条件删除(多条件自动 AND 连接) +affected := database.Delete("user", common.Map{ "status": 0, "created_time[<]": "2023-01-01", }) @@ -245,7 +333,7 @@ HoTimeDB提供了链式查询构建器,让查询更加直观: ```go // 基本链式查询 -users := db.Table("user"). +users := database.Table("user"). Where("status", 1). And("age[>]", 18). Order("created_time DESC"). @@ -253,12 +341,12 @@ users := db.Table("user"). Select() // 链式获取单条记录 -user := db.Table("user"). +user := database.Table("user"). Where("id", 1). Get() // 链式更新 -affected := db.Table("user"). +affected := database.Table("user"). Where("id", 1). Update(common.Map{ "name": "新名称", @@ -266,12 +354,12 @@ affected := db.Table("user"). }) // 链式删除 -affected := db.Table("user"). +affected := database.Table("user"). Where("status", 0). Delete() // 链式统计 -count := db.Table("user"). +count := database.Table("user"). Where("status", 1). Count() ``` @@ -280,7 +368,7 @@ count := db.Table("user"). ```go // 复杂条件组合 -users := db.Table("user"). +users := database.Table("user"). Where("status", 1). And("age[>=]", 18). Or(common.Map{ @@ -289,7 +377,9 @@ users := db.Table("user"). }). 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") ``` @@ -384,24 +474,24 @@ HoTimeDB支持丰富的条件查询语法,类似于Medoo: ```go // LEFT JOIN -users := db.Table("user"). +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 := db.Table("user"). +users := database.Table("user"). RightJoin("order", "user.id = order.user_id"). Select() // INNER JOIN -users := db.Table("user"). +users := database.Table("user"). InnerJoin("profile", "user.id = profile.user_id"). Select() // FULL JOIN -users := db.Table("user"). +users := database.Table("user"). FullJoin("profile", "user.id = profile.user_id"). Select() ``` @@ -409,7 +499,7 @@ users := db.Table("user"). ### 传统JOIN语法 ```go -users := db.Select("user", +users := database.Select("user", common.Slice{ common.Map{"[>]profile": "user.id = profile.user_id"}, common.Map{"[>]department": "user.dept_id = department.id"}, @@ -434,12 +524,12 @@ users := db.Select("user", ```go // 设置分页:页码3,每页20条 -users := db.Page(3, 20).PageSelect("user", "*", common.Map{ +users := database.Page(3, 20).PageSelect("user", "*", common.Map{ "status": 1, }) // 链式分页 -users := db.Table("user"). +users := database.Table("user"). Where("status", 1). Page(2, 15). // 第2页,每页15条 Select() @@ -449,7 +539,7 @@ users := db.Table("user"). ```go // 获取总数 -total := db.Count("user", common.Map{ +total := database.Count("user", common.Map{ "status": 1, }) @@ -468,15 +558,15 @@ fmt.Printf("总记录数: %d, 总页数: %d, 当前页: %d\n", total, totalPages ```go // 总数统计 -total := db.Count("user") +total := database.Count("user") // 条件统计 -activeUsers := db.Count("user", common.Map{ +activeUsers := database.Count("user", common.Map{ "status": 1, }) // JOIN统计 -count := db.Count("user", +count := database.Count("user", common.Slice{ common.Map{"[>]profile": "user.id = profile.user_id"}, }, @@ -491,31 +581,47 @@ count := db.Count("user", ```go // 基本求和 -totalAmount := db.Sum("order", "amount") +totalAmount := database.Sum("order", "amount") // 条件求和 -paidAmount := db.Sum("order", "amount", common.Map{ +paidAmount := database.Sum("order", "amount", common.Map{ "status": "paid", "created_time[>]": "2023-01-01", }) +``` -// JOIN求和 -sum := db.Sum("order", "amount", - common.Slice{ - common.Map{"[>]user": "order.user_id = user.id"}, - }, - common.Map{ - "user.level": "vip", - "order.status": "paid", - }, -) +### 平均值 + +```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 := db.Action(func(tx db.HoTimeDB) bool { +success := database.Action(func(tx db.HoTimeDB) bool { // 在事务中执行多个操作 // 扣减用户余额 @@ -541,18 +647,6 @@ success := db.Action(func(tx db.HoTimeDB) bool { return false // 回滚 } - // 添加订单详情 - detailId := tx.Insert("order_detail", common.Map{ - "order_id": orderId, - "product_id": 1001, - "quantity": 1, - "price": 100, - }) - - if detailId == 0 { - return false // 回滚 - } - return true // 提交 }) @@ -560,7 +654,7 @@ if success { fmt.Println("事务执行成功") } else { fmt.Println("事务回滚") - fmt.Println("错误:", db.LastErr.GetError()) + fmt.Println("错误:", database.LastErr.GetError()) } ``` @@ -574,7 +668,7 @@ HoTimeDB集成了缓存功能,可以自动缓存查询结果: import "code.hoteas.com/golang/hotime/cache" // 设置缓存 -db.HoTimeCache = &cache.HoTimeCache{ +database.HoTimeCache = &cache.HoTimeCache{ // 缓存配置 } ``` @@ -590,21 +684,55 @@ db.HoTimeCache = &cache.HoTimeCache{ ```go // 手动清除表缓存 -db.HoTimeCache.Db("user*", nil) // 清除user表所有缓存 +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 // 设置调试模式 -db.Mode = 2 +database.Mode = 2 // 查看最后执行的SQL -fmt.Println("最后的SQL:", db.LastQuery) -fmt.Println("参数:", db.LastData) -fmt.Println("错误:", db.LastErr.GetError()) +fmt.Println("最后的SQL:", database.LastQuery) +fmt.Println("参数:", database.LastData) +fmt.Println("错误:", database.LastErr.GetError()) ``` ### 主从分离 @@ -620,7 +748,7 @@ func createConnection() (master, slave *sql.DB) { return master, slave } -db.SetConnect(createConnection) +database.SetConnect(createConnection) // 查询会自动使用从库,增删改使用主库 ``` @@ -628,97 +756,16 @@ db.SetConnect(createConnection) ```go // 执行查询SQL -results := db.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1) +results := database.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1) // 执行更新SQL -result, err := db.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1) +result, err := database.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1) if err.GetError() == nil { affected, _ := result.RowsAffected() fmt.Println("影响行数:", affected) } ``` -### 数据类型处理 - -```go -// 时间戳插入 -db.Insert("log", common.Map{ - "user_id": 1, - "action": "login", - "created_time[#]": "UNIX_TIMESTAMP()", -}) - -// JSON数据 -db.Insert("user", common.Map{ - "name": "张三", - "preferences": `{"theme": "dark", "language": "zh-CN"}`, -}) -``` - -### 数据行处理 - -HoTimeDB内部的`Row`方法会自动处理不同数据类型: - -```go -// 自动转换[]uint8为字符串 -// 保持其他类型的原始值 -// 处理NULL值 -``` - -## 错误处理 - -```go -// 检查错误 -users := db.Select("user", "*", common.Map{"status": 1}) -if db.LastErr.GetError() != nil { - fmt.Println("查询错误:", db.LastErr.GetError()) - return -} - -// 插入时检查错误 -id := db.Insert("user", common.Map{ - "name": "test", - "email": "test@example.com", -}) - -if id == 0 && db.LastErr.GetError() != nil { - fmt.Println("插入失败:", db.LastErr.GetError()) -} -``` - -## 性能优化 - -### IN查询优化 - -HoTimeDB会自动优化IN查询,将连续的数字转换为BETWEEN查询以提高性能: - -```go -// 这个查询会被自动优化(单个IN条件) -users := db.Select("user", "*", common.Map{ - "id": []int{1, 2, 3, 4, 5, 10, 11, 12}, - // 自动转为 (id BETWEEN 1 AND 5) OR (id BETWEEN 10 AND 12) -}) -``` - -### 批量操作 - -```go -// 使用事务进行批量插入 -success := db.Action(func(tx db.HoTimeDB) bool { - for i := 0; i < 1000; i++ { - id := tx.Insert("user", common.Map{ - "name": fmt.Sprintf("User%d", i), - "email": fmt.Sprintf("user%d@example.com", i), - "created_time[#]": "NOW()", - }) - if id == 0 { - return false - } - } - return true -}) -``` - ## 特殊语法详解 ### 条件标记符说明 @@ -741,150 +788,43 @@ success := db.Action(func(tx db.HoTimeDB) bool { | `[#!]` | 不等于直接SQL | `"status[#!]": "1"` | `status != 1` | | `[!#]` | 不等于直接SQL | `"status[!#]": "1"` | `status != 1` | -## 与PHP Medoo的差异 +### 特殊关键字(支持大小写) -1. **类型系统**: Golang的强类型要求使用`common.Map`和`common.Slice` -2. **语法差异**: 某些条件语法可能略有不同 -3. **错误处理**: 使用Golang的错误处理模式 -4. **并发安全**: 需要注意并发使用时的安全性 -5. **缓存集成**: 内置了缓存功能 -6. **链式调用**: 提供了更丰富的链式API - -## 完整示例 - -```go -package main - -import ( - "fmt" - "database/sql" - "code.hoteas.com/golang/hotime/db" - "code.hoteas.com/golang/hotime/common" - _ "github.com/go-sql-driver/mysql" -) - -func main() { - // 初始化数据库 - database := &db.HoTimeDB{ - Prefix: "app_", - Mode: 2, // 开发模式 - } - - database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) { - master, _ = sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb") - return master, master - }) - - // 查询用户列表 - users := database.Table("user"). - Where("status", 1). - And("age[>=]", 18). - Order("created_time DESC"). - Limit(0, 10). - Select("id,name,email,age") - - for _, user := range users { - fmt.Printf("用户: %s, 邮箱: %s, 年龄: %v\n", - user.GetString("name"), - user.GetString("email"), - user.Get("age")) - } - - // 创建新用户 - userId := database.Insert("user", common.Map{ - "name": "新用户", - "email": "new@example.com", - "age": 25, - "status": 1, - "created_time[#]": "NOW()", - }) - - fmt.Printf("创建用户ID: %d\n", userId) - - // 更新用户 - affected := database.Table("user"). - Where("id", userId). - Update(common.Map{ - "last_login[#]": "NOW()", - "login_count[#]": "login_count + 1", - }) - - fmt.Printf("更新记录数: %d\n", affected) - - // 复杂查询示例 - orders := 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(common.Map{ - "OR": common.Map{ - "user.level": "vip", - "order.amount[>]": 1000, - }, - }). - Order("order.created_time DESC"). - Group("user.id"). - Select("user.name, user.email, SUM(order.amount) as total_amount, COUNT(*) as order_count") - - // 事务示例 - success := database.Action(func(tx db.HoTimeDB) bool { - // 在事务中执行操作 - orderId := tx.Insert("order", common.Map{ - "user_id": userId, - "total": 100.50, - "status": "pending", - "created_time[#]": "NOW()", - }) - - if orderId == 0 { - return false - } - - detailId := tx.Insert("order_detail", common.Map{ - "order_id": orderId, - "product_id": 1, - "quantity": 2, - "price": 50.25, - }) - - return detailId > 0 - }) - - if success { - fmt.Println("订单创建成功") - } else { - fmt.Println("订单创建失败:", database.LastErr.GetError()) - } - - // 统计示例 - totalUsers := database.Count("user", common.Map{"status": 1}) - totalAmount := database.Sum("order", "amount", common.Map{"status": "paid"}) - - fmt.Printf("活跃用户数: %d, 总交易额: %.2f\n", totalUsers, totalAmount) -} -``` +| 关键字 | 功能 | 示例 | +|--------|------|------| +| `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: 如何处理事务中的错误? -A1: 在`Action`函数中返回`false`即可触发回滚,所有操作都会被撤销。 +### Q1: 多条件查询需要用 AND 包装吗? +A1: 不再需要!现在多条件会自动用 AND 连接。当然,使用 `AND` 包装仍然有效(向后兼容)。 -### Q2: 缓存何时会被清除? -A2: 执行`Insert`、`Update`、`Delete`操作时会自动清除对应表的缓存。 +### Q2: 如何处理事务中的错误? +A2: 在 `Action` 函数中返回 `false` 即可触发回滚,所有操作都会被撤销。 -### Q3: 如何执行复杂的原生SQL? -A3: 使用`Query`方法执行查询,使用`Exec`方法执行更新操作。 +### Q3: 缓存何时会被清除? +A3: 执行 `Insert`、`Update`、`Delete`、`Upsert`、`BatchInsert` 操作时会自动清除对应表的缓存。 -### Q4: 主从分离如何工作? -A4: 查询操作自动使用从库(如果配置了),增删改操作使用主库。 +### Q4: 如何执行复杂的原生SQL? +A4: 使用 `Query` 方法执行查询,使用 `Exec` 方法执行更新操作。 -### Q5: 如何处理NULL值? -A5: 使用`nil`作为值,查询时使用`"field": nil`表示`IS NULL`。 +### Q5: 主从分离如何工作? +A5: 查询操作自动使用从库(如果配置了),增删改操作使用主库。 + +### Q6: 如何处理NULL值? +A6: 使用 `nil` 作为值,查询时使用 `"field": nil` 表示 `IS NULL`。 + +### Q7: PostgreSQL 和 MySQL 语法有区别吗? +A7: 框架会自动处理差异(占位符、引号等),代码无需修改。 --- -*文档版本: 1.0* -*最后更新: 2024年* +*文档版本: 2.0* +*最后更新: 2026年1月* -> 本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,但根据Golang语言特性进行了适配和优化。 \ No newline at end of file +> 本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,但根据Golang语言特性进行了适配和优化。 diff --git a/db/aggregate.go b/db/aggregate.go new file mode 100644 index 0000000..a1d24fd --- /dev/null +++ b/db/aggregate.go @@ -0,0 +1,115 @@ +package db + +import ( + . "code.hoteas.com/golang/hotime/common" +) + +// Count 计数 +func (that *HoTimeDB) Count(table string, qu ...interface{}) int { + var req = []interface{}{} + if len(qu) == 2 { + req = append(req, qu[0]) + req = append(req, "COUNT(*)") + req = append(req, qu[1]) + } else { + req = append(req, "COUNT(*)") + req = append(req, qu...) + } + + data := that.Select(table, req...) + if len(data) == 0 { + return 0 + } + + res := ObjToStr(data[0]["COUNT(*)"]) + count, _ := StrToInt(res) + return count +} + +// Sum 求和 +func (that *HoTimeDB) Sum(table string, column string, qu ...interface{}) float64 { + var req = []interface{}{} + if len(qu) == 2 { + req = append(req, qu[0]) + req = append(req, "SUM("+column+")") + req = append(req, qu[1]) + } else { + req = append(req, "SUM("+column+")") + req = append(req, qu...) + } + + data := that.Select(table, req...) + if len(data) == 0 { + return 0 + } + + res := ObjToStr(data[0]["SUM("+column+")"]) + sum := ObjToFloat64(res) + return sum +} + +// Avg 平均值 +func (that *HoTimeDB) Avg(table string, column string, qu ...interface{}) float64 { + var req = []interface{}{} + if len(qu) == 2 { + req = append(req, qu[0]) + req = append(req, "AVG("+column+")") + req = append(req, qu[1]) + } else { + req = append(req, "AVG("+column+")") + req = append(req, qu...) + } + + data := that.Select(table, req...) + if len(data) == 0 { + return 0 + } + + res := ObjToStr(data[0]["AVG("+column+")"]) + avg := ObjToFloat64(res) + return avg +} + +// Max 最大值 +func (that *HoTimeDB) Max(table string, column string, qu ...interface{}) float64 { + var req = []interface{}{} + if len(qu) == 2 { + req = append(req, qu[0]) + req = append(req, "MAX("+column+")") + req = append(req, qu[1]) + } else { + req = append(req, "MAX("+column+")") + req = append(req, qu...) + } + + data := that.Select(table, req...) + if len(data) == 0 { + return 0 + } + + res := ObjToStr(data[0]["MAX("+column+")"]) + max := ObjToFloat64(res) + return max +} + +// Min 最小值 +func (that *HoTimeDB) Min(table string, column string, qu ...interface{}) float64 { + var req = []interface{}{} + if len(qu) == 2 { + req = append(req, qu[0]) + req = append(req, "MIN("+column+")") + req = append(req, qu[1]) + } else { + req = append(req, "MIN("+column+")") + req = append(req, qu...) + } + + data := that.Select(table, req...) + if len(data) == 0 { + return 0 + } + + res := ObjToStr(data[0]["MIN("+column+")"]) + min := ObjToFloat64(res) + return min +} diff --git a/db/backup.go b/db/backup.go new file mode 100644 index 0000000..e50b466 --- /dev/null +++ b/db/backup.go @@ -0,0 +1,93 @@ +package db + +import ( + . "code.hoteas.com/golang/hotime/common" + "os" + "strings" +) + +// backupSave 保存备份 +// path: 备份文件路径 +// tt: 表名 +// code: 备份类型 0=全部, 1=仅数据, 2=仅DDL +func (that *HoTimeDB) backupSave(path string, tt string, code int) { + fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + defer fd.Close() + + str := "\r\n" + if code == 0 || code == 2 { + str += that.backupDdl(tt) + } + + if code == 0 || code == 1 { + str += "insert into `" + tt + "`\r\n\r\n(" + str += that.backupCol(tt) + } + + _, _ = fd.Write([]byte(str)) +} + +// backupDdl 备份表结构(DDL) +func (that *HoTimeDB) backupDdl(tt string) string { + data := that.Query("show create table " + tt) + if len(data) == 0 { + return "" + } + + return ObjToStr(data[0]["Create Table"]) + ";\r\n\r\n" +} + +// backupCol 备份表数据 +func (that *HoTimeDB) backupCol(tt string) string { + str := "" + data := that.Select(tt, "*") + + lthData := len(data) + + if lthData == 0 { + return str + } + + lthCol := len(data[0]) + col := make([]string, lthCol) + tempLthData := 0 + + for k := range data[0] { + if tempLthData == lthCol-1 { + str += "`" + k + "`) " + } else { + str += "`" + k + "`," + } + col[tempLthData] = k + tempLthData++ + } + + str += " values" + + for j := 0; j < lthData; j++ { + for m := 0; m < lthCol; m++ { + if m == 0 { + str += "(" + } + + v := "NULL" + if data[j][col[m]] != nil { + v = "'" + strings.Replace(ObjToStr(data[j][col[m]]), "'", `\'`, -1) + "'" + } + + if m == lthCol-1 { + str += v + ") " + } else { + str += v + "," + } + } + + if j == lthData-1 { + str += ";\r\n\r\n" + } else { + str += ",\r\n\r\n" + } + } + + return str +} diff --git a/db/builder.go b/db/builder.go new file mode 100644 index 0000000..3400521 --- /dev/null +++ b/db/builder.go @@ -0,0 +1,273 @@ +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 { + return that.HoTimeDB.Get(that.table, that.join, qu, that.where) +} + +// Count 统计数量 +func (that *HotimeDBBuilder) Count() int { + return that.HoTimeDB.Count(that.table, that.join, 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 { + if that.page != 0 { + return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, that.join, qu, that.where) + } + return that.HoTimeDB.Select(that.table, that.join, qu, that.where) +} + +// 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 +} diff --git a/db/crud.go b/db/crud.go new file mode 100644 index 0000000..a5ab209 --- /dev/null +++ b/db/crud.go @@ -0,0 +1,660 @@ +package db + +import ( + "reflect" + "sort" + "strings" + + . "code.hoteas.com/golang/hotime/common" +) + +// Page 设置分页参数 +// page: 页码(从1开始) +// pageRow: 每页数量 +func (that *HoTimeDB) Page(page, pageRow int) *HoTimeDB { + if page < 1 { + page = 1 + } + if pageRow < 1 { + pageRow = 10 + } + offset := (page - 1) * pageRow + + that.limitMu.Lock() + that.limit = Slice{offset, pageRow} + that.limitMu.Unlock() + return that +} + +// PageSelect 分页查询 +func (that *HoTimeDB) PageSelect(table string, qu ...interface{}) []Map { + that.limitMu.Lock() + limit := that.limit + that.limit = nil // 使用后清空,避免影响下次调用 + that.limitMu.Unlock() + + if limit == nil { + return that.Select(table, qu...) + } + + // 根据参数数量处理 LIMIT 注入 + switch len(qu) { + case 0: + // PageSelect("user") -> 只有表名,添加 LIMIT + qu = append(qu, "*", Map{"LIMIT": limit}) + case 1: + // PageSelect("user", "*") 或 PageSelect("user", Map{...}) + if reflect.ValueOf(qu[0]).Kind() == reflect.Map { + // 是 where 条件 + temp := DeepCopyMap(qu[0]).(Map) + temp["LIMIT"] = limit + qu[0] = temp + } else { + // 是字段选择 + qu = append(qu, Map{"LIMIT": limit}) + } + case 2: + // PageSelect("user", "*", Map{...}) 或 PageSelect("user", joinSlice, "*") + if reflect.ValueOf(qu[1]).Kind() == reflect.Map { + temp := DeepCopyMap(qu[1]).(Map) + temp["LIMIT"] = limit + qu[1] = temp + } else { + // join 模式,需要追加 where + qu = append(qu, Map{"LIMIT": limit}) + } + case 3: + // PageSelect("user", joinSlice, "*", Map{...}) + temp := DeepCopyMap(qu[2]).(Map) + temp["LIMIT"] = limit + qu[2] = temp + } + + return that.Select(table, qu...) +} + +// Select 查询多条记录 +func (that *HoTimeDB) Select(table string, qu ...interface{}) []Map { + query := "SELECT" + where := Map{} + qs := make([]interface{}, 0) + intQs, intWhere := 0, 1 + join := false + + if len(qu) == 3 { + intQs = 1 + intWhere = 2 + join = true + } + + if len(qu) > 0 { + if reflect.ValueOf(qu[intQs]).Type().String() == "string" { + query += " " + qu[intQs].(string) + } else { + data := ObjToSlice(qu[intQs]) + for i := 0; i < len(data); i++ { + k := data.GetString(i) + if strings.Contains(k, " AS ") || strings.Contains(k, ".") { + query += " " + k + " " + } else { + query += " `" + k + "` " + } + + if i+1 != len(data) { + query = query + ", " + } + } + } + } else { + query += " *" + } + + if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") { + query += " FROM `" + that.Prefix + table + "` " + } else { + query += " FROM " + that.Prefix + table + " " + } + + if join { + query += that.buildJoin(qu[0]) + } + + if len(qu) > 1 { + where = qu[intWhere].(Map) + } + + temp, resWhere := that.where(where) + + query += temp + ";" + qs = append(qs, resWhere...) + md5 := that.md5(query, qs...) + + if that.HoTimeCache != nil && table != "cached" { + // 如果缓存有则从缓存取 + cacheData := that.HoTimeCache.Db(table + ":" + md5) + if cacheData != nil && cacheData.Data != nil { + return cacheData.ToMapArray() + } + } + + // 无缓存则数据库取 + res := that.Query(query, qs...) + + if res == nil { + res = []Map{} + } + + // 缓存 + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+":"+md5, res) + } + + return res +} + +// buildJoin 构建 JOIN 语句 +func (that *HoTimeDB) buildJoin(joinData interface{}) string { + query := "" + var testQu = []string{} + testQuData := Map{} + + if reflect.ValueOf(joinData).Type().String() == "common.Map" { + testQuData = joinData.(Map) + for key := range testQuData { + testQu = append(testQu, key) + } + } + + if reflect.ValueOf(joinData).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(joinData).Type().String(), "[]") { + qu0 := ObjToSlice(joinData) + for key := range qu0 { + v := qu0.GetMap(key) + for k1, v1 := range v { + testQu = append(testQu, k1) + testQuData[k1] = v1 + } + } + } + + sort.Strings(testQu) + + for _, k := range testQu { + v := testQuData[k] + switch Substr(k, 0, 3) { + case "[>]": + func() { + table := Substr(k, 3, len(k)-3) + if !strings.Contains(table, " ") { + table = "`" + table + "`" + } + query += " LEFT JOIN " + table + " ON " + v.(string) + " " + }() + case "[<]": + func() { + table := Substr(k, 3, len(k)-3) + if !strings.Contains(table, " ") { + table = "`" + table + "`" + } + query += " RIGHT JOIN " + table + " ON " + v.(string) + " " + }() + } + switch Substr(k, 0, 4) { + case "[<>]": + func() { + table := Substr(k, 4, len(k)-4) + if !strings.Contains(table, " ") { + table = "`" + table + "`" + } + query += " FULL JOIN " + table + " ON " + v.(string) + " " + }() + case "[><]": + func() { + table := Substr(k, 4, len(k)-4) + if !strings.Contains(table, " ") { + table = "`" + table + "`" + } + query += " INNER JOIN " + table + " ON " + v.(string) + " " + }() + } + } + + return query +} + +// Get 获取单条记录 +func (that *HoTimeDB) Get(table string, qu ...interface{}) Map { + if len(qu) == 1 { + qu = append(qu, Map{"LIMIT": 1}) + } + if len(qu) == 2 { + temp := qu[1].(Map) + temp["LIMIT"] = 1 + qu[1] = temp + } + if len(qu) == 3 { + temp := qu[2].(Map) + temp["LIMIT"] = 1 + qu[2] = temp + } + + data := that.Select(table, qu...) + if len(data) == 0 { + return nil + } + return data[0] +} + +// Insert 插入新数据 +func (that *HoTimeDB) Insert(table string, data map[string]interface{}) int64 { + values := make([]interface{}, 0) + queryString := " (" + valueString := " (" + + lens := len(data) + tempLen := 0 + + for k, v := range data { + tempLen++ + + vstr := "?" + if Substr(k, len(k)-3, 3) == "[#]" { + k = strings.Replace(k, "[#]", "", -1) + vstr = ObjToStr(v) + if tempLen < lens { + queryString += "`" + k + "`," + valueString += vstr + "," + } else { + queryString += "`" + k + "`) " + valueString += vstr + ");" + } + } else { + values = append(values, v) + if tempLen < lens { + queryString += "`" + k + "`," + valueString += "?," + } else { + queryString += "`" + k + "`) " + valueString += "?);" + } + } + } + + query := "INSERT INTO `" + that.Prefix + table + "` " + queryString + "VALUES" + valueString + + res, err := that.Exec(query, values...) + + id := int64(0) + if err.GetError() == nil && res != nil { + id1, err := res.LastInsertId() + that.LastErr.SetError(err) + id = id1 + } + + // 如果插入成功,删除缓存 + if id != 0 { + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+"*", nil) + } + } + + return id +} + +// BatchInsert 批量插入数据 +// table: 表名 +// dataList: 数据列表,每个元素是一个 Map +// 返回受影响的行数 +// +// 示例: +// +// affected := db.BatchInsert("user", []Map{ +// {"name": "张三", "age": 25, "email": "zhang@example.com"}, +// {"name": "李四", "age": 30, "email": "li@example.com"}, +// {"name": "王五", "age": 28, "email": "wang@example.com"}, +// }) +func (that *HoTimeDB) BatchInsert(table string, dataList []Map) int64 { + if len(dataList) == 0 { + return 0 + } + + // 从第一条数据提取所有列名(确保顺序一致) + columns := make([]string, 0) + rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值 + + for k := range dataList[0] { + realKey := k + if Substr(k, len(k)-3, 3) == "[#]" { + realKey = strings.Replace(k, "[#]", "", -1) + rawValues[realKey] = ObjToStr(dataList[0][k]) + } + columns = append(columns, realKey) + } + + // 排序列名以确保一致性 + sort.Strings(columns) + + // 构建列名部分 + quotedCols := make([]string, len(columns)) + for i, col := range columns { + quotedCols[i] = "`" + col + "`" + } + colStr := strings.Join(quotedCols, ", ") + + // 构建每行的占位符和值 + placeholders := make([]string, len(dataList)) + values := make([]interface{}, 0, len(dataList)*len(columns)) + + for i, data := range dataList { + rowPlaceholders := make([]string, len(columns)) + for j, col := range columns { + // 检查是否有 [#] 标记 + rawKey := col + "[#]" + if rawVal, ok := data[rawKey]; ok { + // 直接 SQL 表达式 + rowPlaceholders[j] = ObjToStr(rawVal) + } else if _, isRaw := rawValues[col]; isRaw && i == 0 { + // 第一条数据中的 [#] 标记 + rowPlaceholders[j] = rawValues[col] + } else if val, ok := data[col]; ok { + // 普通值 + rowPlaceholders[j] = "?" + values = append(values, val) + } else { + // 字段不存在,使用 NULL + rowPlaceholders[j] = "NULL" + } + } + placeholders[i] = "(" + strings.Join(rowPlaceholders, ", ") + ")" + } + + query := "INSERT INTO `" + that.Prefix + table + "` (" + colStr + ") VALUES " + strings.Join(placeholders, ", ") + + res, err := that.Exec(query, values...) + + rows64 := int64(0) + if err.GetError() == nil && res != nil { + rows64, _ = res.RowsAffected() + } + + // 如果插入成功,删除缓存 + if rows64 != 0 { + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+"*", nil) + } + } + + return rows64 +} + +// Upsert 插入或更新数据 +// table: 表名 +// data: 要插入的数据 +// uniqueKeys: 唯一键字段(用于冲突检测),支持 Slice{"id"} 或 Slice{"col1", "col2"} +// updateColumns: 冲突时要更新的字段(如果为空,则更新所有非唯一键字段) +// 返回受影响的行数 +// +// 示例: +// +// affected := db.Upsert("user", +// Map{"id": 1, "name": "张三", "email": "zhang@example.com"}, +// Slice{"id"}, // 唯一键 +// Slice{"name", "email"}, // 冲突时更新的字段 +// ) +func (that *HoTimeDB) Upsert(table string, data Map, uniqueKeys Slice, updateColumns ...interface{}) int64 { + if len(data) == 0 || len(uniqueKeys) == 0 { + return 0 + } + + // 转换 uniqueKeys 为 []string + uniqueKeyStrs := make([]string, len(uniqueKeys)) + for i, uk := range uniqueKeys { + uniqueKeyStrs[i] = ObjToStr(uk) + } + + // 转换 updateColumns 为 []string + var updateColumnStrs []string + if len(updateColumns) > 0 { + // 支持两种调用方式:Upsert(table, data, Slice{"id"}, Slice{"name"}) 或 Upsert(table, data, Slice{"id"}, "name", "email") + if slice, ok := updateColumns[0].(Slice); ok { + updateColumnStrs = make([]string, len(slice)) + for i, col := range slice { + updateColumnStrs[i] = ObjToStr(col) + } + } else { + updateColumnStrs = make([]string, len(updateColumns)) + for i, col := range updateColumns { + updateColumnStrs[i] = ObjToStr(col) + } + } + } + + // 收集列和值 + columns := make([]string, 0, len(data)) + values := make([]interface{}, 0, len(data)) + rawValues := make(map[string]string) // 存储 [#] 标记的直接 SQL 值 + + for k, v := range data { + if Substr(k, len(k)-3, 3) == "[#]" { + realKey := strings.Replace(k, "[#]", "", -1) + columns = append(columns, realKey) + rawValues[realKey] = ObjToStr(v) + } else { + columns = append(columns, k) + values = append(values, v) + } + } + + // 如果没有指定更新字段,则更新所有非唯一键字段 + if len(updateColumnStrs) == 0 { + uniqueKeySet := make(map[string]bool) + for _, uk := range uniqueKeyStrs { + uniqueKeySet[uk] = true + } + for _, col := range columns { + if !uniqueKeySet[col] { + updateColumnStrs = append(updateColumnStrs, col) + } + } + } + + // 构建 SQL + var query string + dbType := that.Type + if dbType == "" { + dbType = "mysql" + } + + switch dbType { + case "postgres", "postgresql": + query = that.buildPostgresUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues) + case "sqlite3", "sqlite": + query = that.buildSQLiteUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues) + default: // mysql + query = that.buildMySQLUpsert(table, columns, uniqueKeyStrs, updateColumnStrs, rawValues) + } + + res, err := that.Exec(query, values...) + + rows := int64(0) + if err.GetError() == nil && res != nil { + rows, _ = res.RowsAffected() + } + + // 清除缓存 + if rows != 0 { + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+"*", nil) + } + } + + return rows +} + +// buildMySQLUpsert 构建 MySQL 的 Upsert 语句 +func (that *HoTimeDB) buildMySQLUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string { + // INSERT INTO table (col1, col2) VALUES (?, ?) + // ON DUPLICATE KEY UPDATE col1 = VALUES(col1), col2 = VALUES(col2) + + quotedCols := make([]string, len(columns)) + valueParts := make([]string, len(columns)) + for i, col := range columns { + quotedCols[i] = "`" + col + "`" + if raw, ok := rawValues[col]; ok { + valueParts[i] = raw + } else { + valueParts[i] = "?" + } + } + + updateParts := make([]string, len(updateColumns)) + for i, col := range updateColumns { + if raw, ok := rawValues[col]; ok { + updateParts[i] = "`" + col + "` = " + raw + } else { + updateParts[i] = "`" + col + "` = VALUES(`" + col + "`)" + } + } + + return "INSERT INTO `" + that.Prefix + table + "` (" + strings.Join(quotedCols, ", ") + + ") VALUES (" + strings.Join(valueParts, ", ") + + ") ON DUPLICATE KEY UPDATE " + strings.Join(updateParts, ", ") +} + +// buildPostgresUpsert 构建 PostgreSQL 的 Upsert 语句 +func (that *HoTimeDB) buildPostgresUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string { + // INSERT INTO table (col1, col2) VALUES ($1, $2) + // ON CONFLICT (unique_key) DO UPDATE SET col1 = EXCLUDED.col1 + + quotedCols := make([]string, len(columns)) + valueParts := make([]string, len(columns)) + paramIndex := 1 + for i, col := range columns { + quotedCols[i] = "\"" + col + "\"" + if raw, ok := rawValues[col]; ok { + valueParts[i] = raw + } else { + valueParts[i] = "$" + ObjToStr(paramIndex) + paramIndex++ + } + } + + quotedUniqueKeys := make([]string, len(uniqueKeys)) + for i, key := range uniqueKeys { + quotedUniqueKeys[i] = "\"" + key + "\"" + } + + updateParts := make([]string, len(updateColumns)) + for i, col := range updateColumns { + if raw, ok := rawValues[col]; ok { + updateParts[i] = "\"" + col + "\" = " + raw + } else { + updateParts[i] = "\"" + col + "\" = EXCLUDED.\"" + col + "\"" + } + } + + return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") + + ") VALUES (" + strings.Join(valueParts, ", ") + + ") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") + + ") DO UPDATE SET " + strings.Join(updateParts, ", ") +} + +// buildSQLiteUpsert 构建 SQLite 的 Upsert 语句 +func (that *HoTimeDB) buildSQLiteUpsert(table string, columns []string, uniqueKeys []string, updateColumns []string, rawValues map[string]string) string { + // INSERT INTO table (col1, col2) VALUES (?, ?) + // ON CONFLICT (unique_key) DO UPDATE SET col1 = excluded.col1 + + quotedCols := make([]string, len(columns)) + valueParts := make([]string, len(columns)) + for i, col := range columns { + quotedCols[i] = "\"" + col + "\"" + if raw, ok := rawValues[col]; ok { + valueParts[i] = raw + } else { + valueParts[i] = "?" + } + } + + quotedUniqueKeys := make([]string, len(uniqueKeys)) + for i, key := range uniqueKeys { + quotedUniqueKeys[i] = "\"" + key + "\"" + } + + updateParts := make([]string, len(updateColumns)) + for i, col := range updateColumns { + if raw, ok := rawValues[col]; ok { + updateParts[i] = "\"" + col + "\" = " + raw + } else { + updateParts[i] = "\"" + col + "\" = excluded.\"" + col + "\"" + } + } + + return "INSERT INTO \"" + that.Prefix + table + "\" (" + strings.Join(quotedCols, ", ") + + ") VALUES (" + strings.Join(valueParts, ", ") + + ") ON CONFLICT (" + strings.Join(quotedUniqueKeys, ", ") + + ") DO UPDATE SET " + strings.Join(updateParts, ", ") +} + +// Update 更新数据 +func (that *HoTimeDB) Update(table string, data Map, where Map) int64 { + query := "UPDATE `" + that.Prefix + table + "` SET " + qs := make([]interface{}, 0) + tp := len(data) + + for k, v := range data { + vstr := "?" + if Substr(k, len(k)-3, 3) == "[#]" { + k = strings.Replace(k, "[#]", "", -1) + vstr = ObjToStr(v) + } else { + qs = append(qs, v) + } + query += "`" + k + "`=" + vstr + " " + if tp--; tp != 0 { + query += ", " + } + } + + temp, resWhere := that.where(where) + + query += temp + ";" + qs = append(qs, resWhere...) + + res, err := that.Exec(query, qs...) + + rows := int64(0) + if err.GetError() == nil && res != nil { + rows, _ = res.RowsAffected() + } + + // 如果更新成功,则删除缓存 + if rows != 0 { + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+"*", nil) + } + } + + return rows +} + +// Delete 删除数据 +func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 { + query := "DELETE FROM `" + that.Prefix + table + "` " + + temp, resWhere := that.where(data) + query += temp + ";" + + res, err := that.Exec(query, resWhere...) + rows := int64(0) + if err.GetError() == nil && res != nil { + rows, _ = res.RowsAffected() + } + + // 如果删除成功,删除对应缓存 + if rows != 0 { + if that.HoTimeCache != nil && table != "cached" { + _ = that.HoTimeCache.Db(table+"*", nil) + } + } + + return rows +} diff --git a/db/hotimedb.go b/db/hotimedb.go deleted file mode 100644 index 63388cb..0000000 --- a/db/hotimedb.go +++ /dev/null @@ -1,1680 +0,0 @@ -package db - -import ( - "code.hoteas.com/golang/hotime/cache" - . "code.hoteas.com/golang/hotime/common" - "database/sql" - "encoding/json" - "errors" - _ "github.com/go-sql-driver/mysql" - _ "github.com/mattn/go-sqlite3" - "github.com/sirupsen/logrus" - "os" - "reflect" - "sort" - "strings" - "sync" -) - -type HoTimeDB struct { - *sql.DB - ContextBase - DBName string - *cache.HoTimeCache - Log *logrus.Logger - Type string - 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 -} - -type HotimeDBBuilder struct { - HoTimeDB *HoTimeDB - table string - selects []interface{} - join Slice - where Map - lastWhere Map - page int - pageRow int -} - -func (that *HoTimeDB) Table(table string) *HotimeDBBuilder { - - return &HotimeDBBuilder{HoTimeDB: that, table: table, where: Map{}} -} - -func (that *HotimeDBBuilder) Get(qu ...interface{}) Map { - - return that.HoTimeDB.Get(that.table, that.join, qu, that.where) -} - -func (that *HotimeDBBuilder) Count() int { - - return that.HoTimeDB.Count(that.table, that.join, that.where) -} -func (that *HotimeDBBuilder) Page(page, pageRow int) *HotimeDBBuilder { - that.page = page - that.pageRow = pageRow - return that -} -func (that *HotimeDBBuilder) Select(qu ...interface{}) []Map { - if that.page != 0 { - return that.HoTimeDB.Page(that.page, that.pageRow).PageSelect(that.table, that.join, qu, that.where) - } - return that.HoTimeDB.Select(that.table, that.join, qu, that.where) -} - -func (that *HotimeDBBuilder) Update(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) -} - -func (that *HotimeDBBuilder) Delete() int64 { - - return that.HoTimeDB.Delete(that.table, that.where) -} - -func (that *HotimeDBBuilder) LeftJoin(table, joinStr string) *HotimeDBBuilder { - that.Join(Map{"[>]" + table: joinStr}) - return that - -} -func (that *HotimeDBBuilder) RightJoin(table, joinStr string) *HotimeDBBuilder { - that.Join(Map{"[<]" + table: joinStr}) - return that - -} -func (that *HotimeDBBuilder) InnerJoin(table, joinStr string) *HotimeDBBuilder { - that.Join(Map{"[><]" + table: joinStr}) - return that - -} - -func (that *HotimeDBBuilder) FullJoin(table, joinStr string) *HotimeDBBuilder { - that.Join(Map{"[<>]" + table: joinStr}) - return that -} - -func (that *HotimeDBBuilder) Join(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 -} - -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 -} - -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 -} -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 -} - -func (that *HotimeDBBuilder) From(table string) *HotimeDBBuilder { - that.table = table - return that -} - -func (that *HotimeDBBuilder) Order(qu ...interface{}) *HotimeDBBuilder { - that.where["ORDER"] = ObjToSlice(qu) - return that -} -func (that *HotimeDBBuilder) Limit(qu ...interface{}) *HotimeDBBuilder { - that.where["LIMIT"] = ObjToSlice(qu) - return that -} - -func (that *HotimeDBBuilder) Group(qu ...interface{}) *HotimeDBBuilder { - that.where["GROUP"] = ObjToSlice(qu) - return that -} - -// SetConnect 设置数据库配置连接 -func (that *HoTimeDB) SetConnect(connect func(err ...*Error) (master, slave *sql.DB), err ...*Error) { - that.ConnectFunc = connect - _ = that.InitDb(err...) - -} - -// GetType 设置数据库配置连接 -func (that *HoTimeDB) GetType() string { - return that.Type -} - -// 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, - mu: sync.RWMutex{}, - limitMu: sync.Mutex{}, - } - - //tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadCommitted}) - 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 -} - -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) - } - //that.DB.SetConnMaxLifetime(time.Second) - return that.LastErr -} - -func (that *HoTimeDB) Page(page, pageRow int) *HoTimeDB { - page = (page - 1) * pageRow - if page < 0 { - page = 1 - } - - that.limitMu.Lock() - that.limit = Slice{page, pageRow} - that.limitMu.Unlock() - return that -} - -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...) - } - - if len(qu) == 1 { - qu = append(qu, Map{"LIMIT": limit}) - } - if len(qu) == 2 { - temp := DeepCopyMap(qu[1]).(Map) - temp["LIMIT"] = limit - qu[1] = temp - } - if len(qu) == 3 { - temp := DeepCopyMap(qu[2]).(Map) - temp["LIMIT"] = limit - qu[2] = temp - } - //fmt.Println(qu) - data := that.Select(table, qu...) - - return data -} - -// 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++ { - //fmt.Println(reflect.ValueOf(a[j]).Type().String() ) - if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" { - lis[strs[j]] = string(a[j].([]byte)) - } else { - lis[strs[j]] = a[j] //取实际类型 - } - - } - //防止int被误读为float64 - //jlis, e := json.Marshal(lis) - //if e != nil { - // that.LastErr.SetError(e) - //} else { - // lis.JsonToMap(string(jlis), that.LastErr) - //} - - dest = append(dest, lis) - - } - - return dest -} - -// -////code=0,1,2 0 backup all,1 backup data,2 backup ddl -//func (that *HoTimeDB) Backup(path string, code int) { -// var cmd *exec.Cmd -// switch code { -// case 0:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"])) -// case 1:cmd= exec.Command("mysqldump","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]), ObjToStr(Config["dbName"])) -// case 2:cmd= exec.Command("mysqldump","--no-data","-h"+ObjToStr(Config["dbHost"]), "-P"+ObjToStr(Config["dbPort"]),"-u"+ObjToStr(Config["dbUser"]), "-p"+ObjToStr(Config["dbPwd"]),ObjToStr(Config["dbName"])) -// } -// -// -// -// stdout, err := cmd.StdoutPipe() -// if err != nil { -// log.Println(err) -// } -// -// if err := cmd.Start(); err != nil { -// log.Println(err) -// } -// -// bytes, err := ioutil.ReadAll(stdout) -// if err != nil { -// log.Println(err) -// } -// err = ioutil.WriteFile(path, bytes, 0644) -// if err != nil { -// panic(err) -// } -// return ; -// // -// //db := `` -// //fmt.Println(db) -// // -// //tables := that.Query("show tables") -// //lth := len(tables) -// //if lth == 0 { -// // return -// //} -// //for k, _ := range tables[0] { -// // db = Substr(k, 10, len(k)) -// //} -// // -// //fd, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) -// //fd.Write([]byte("/*datetime " + time.Now().Format("2006-01-02 15:04:05") + " */ \r\n")) -// //fd.Close() -// // -// //for i := 0; i < lth; i++ { -// // tt := tables[i]["Tables_in_"+db].(string) -// // that.backupSave(path, tt, code) -// // debug.FreeOSMemory() -// //} -// -//} - -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)) -} -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" -} - -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 -} - -func (that *HoTimeDB) md5(query string, args ...interface{}) string { - strByte, _ := json.Marshal(args) - str := Md5(query + ":" + string(strByte)) - return str -} - -func (that *HoTimeDB) Query(query string, args ...interface{}) []Map { - return that.queryWithRetry(query, false, args...) -} - -// queryWithRetry 内部查询方法,支持重试标记 -func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interface{}) []Map { - // 保存调试信息(加锁保护) - that.mu.Lock() - that.LastQuery = query - that.LastData = args - that.mu.Unlock() - - defer func() { - if that.Mode != 0 { - that.mu.RLock() - that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError()) - that.mu.RUnlock() - } - }() - - var err error - var resl *sql.Rows - - //主从数据库切换,只有select语句有从数据库 - db := that.DB - if that.SlaveDB != nil { - db = that.SlaveDB - } - - if db == nil { - err = errors.New("没有初始化数据库") - that.LastErr.SetError(err) - return nil - } - - // 处理参数中的 slice 类型 - processedArgs := 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 - } - } - - 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) -} - -func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) { - return that.execWithRetry(query, false, args...) -} - -// execWithRetry 内部执行方法,支持重试标记 -func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interface{}) (sql.Result, *Error) { - // 保存调试信息(加锁保护) - that.mu.Lock() - that.LastQuery = query - that.LastData = args - that.mu.Unlock() - - defer func() { - if that.Mode != 0 { - that.mu.RLock() - that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError()) - that.mu.RUnlock() - } - }() - - var e error - var resl sql.Result - - if that.DB == nil { - err := errors.New("没有初始化数据库") - that.LastErr.SetError(err) - return nil, that.LastErr - } - - // 处理参数中的 slice 类型 - processedArgs := make([]interface{}, len(args)) - copy(processedArgs, args) - for key := range processedArgs { - arg := processedArgs[key] - argType := "" - if arg != nil { - 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 - } - } - - 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 -} - -//func (that *HoTimeDB)copy(data []Map)[]Map{ -// if data==nil{ -// return nil -// } -// -// lth:=len(data) -// -// res:=make([]Map,lth) -// -// -// for i:=0;i 0 { - if reflect.ValueOf(qu[intQs]).Type().String() == "string" { - query += " " + qu[intQs].(string) - } else { - data := ObjToSlice(qu[intQs]) - for i := 0; i < len(data); i++ { - k := data.GetString(i) - if strings.Contains(k, " AS ") || - strings.Contains(k, ".") { - - query += " " + k + " " - - } else { - - query += " `" + k + "` " - } - - if i+1 != len(data) { - query = query + ", " - } - - } - } - - } else { - query += " *" - } - if !strings.Contains(table, ".") && !strings.Contains(table, " AS ") { - query += " FROM `" + that.Prefix + table + "` " - } else { - query += " FROM " + that.Prefix + table + " " - } - - if join { - var testQu = []string{} - testQuData := Map{} - if reflect.ValueOf(qu[0]).Type().String() == "common.Map" { - testQuData = qu[0].(Map) - for key, _ := range testQuData { - //fmt.Println(key, ":", value) - testQu = append(testQu, key) - } - } - - if reflect.ValueOf(qu[0]).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(qu[0]).Type().String(), "[]") { - qu0 := ObjToSlice(qu[0]) - for key, _ := range qu0 { - v := qu0.GetMap(key) - for k1, v1 := range v { - testQu = append(testQu, k1) - testQuData[k1] = v1 - } - } - } - - sort.Strings(testQu) - - for _, k := range testQu { - v := testQuData[k] - switch Substr(k, 0, 3) { - case "[>]": - func() { - table := Substr(k, 3, len(k)-3) - if !strings.Contains(table, " ") { - table = "`" + table + "`" - } - query += " LEFT JOIN " + table + " ON " + v.(string) + " " - }() - case "[<]": - func() { - table := Substr(k, 3, len(k)-3) - if !strings.Contains(table, " ") { - table = "`" + table + "`" - } - query += " RIGHT JOIN " + table + " ON " + v.(string) + " " - }() - - } - switch Substr(k, 0, 4) { - case "[<>]": - func() { - table := Substr(k, 4, len(k)-4) - if !strings.Contains(table, " ") { - table = "`" + table + "`" - } - query += " FULL JOIN " + table + " ON " + v.(string) + " " - }() - - case "[><]": - func() { - table := Substr(k, 4, len(k)-4) - if !strings.Contains(table, " ") { - table = "`" + table + "`" - } - query += " INNER JOIN " + table + " ON " + v.(string) + " " - }() - - } - } - } - - 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 - -} - -func (that *HoTimeDB) Get(table string, qu ...interface{}) Map { - //fmt.Println(qu) - if len(qu) == 1 { - qu = append(qu, Map{"LIMIT": 1}) - } - if len(qu) == 2 { - temp := qu[1].(Map) - temp["LIMIT"] = 1 - qu[1] = temp - } - if len(qu) == 3 { - temp := qu[2].(Map) - temp["LIMIT"] = 1 - qu[2] = temp - } - //fmt.Println(qu) - data := that.Select(table, qu...) - if len(data) == 0 { - return nil - } - return data[0] -} -func (that *HoTimeDB) GetPrefix() string { - return that.Prefix -} - -// 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...) - } - - //req=append(req,qu...) - data := that.Select(table, req...) - //fmt.Println(data) - if len(data) == 0 { - return 0 - } - //res,_:=StrToInt(data[0]["COUNT(*)"].(string)) - res := ObjToStr(data[0]["COUNT(*)"]) - count, _ := StrToInt(res) - return count - -} - -var condition = []string{"AND", "OR"} -var vcond = []string{"GROUP", "ORDER", "LIMIT", "DISTINCT"} - -// Count 计数 -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...) - } - - //req=append(req,qu...) - data := that.Select(table, req...) - //fmt.Println(data) - if len(data) == 0 { - return 0 - } - //res,_:=StrToInt(data[0]["COUNT(*)"].(string)) - res := ObjToStr(data[0]["SUM("+column+")"]) - count := ObjToFloat64(res) - return count - -} - -// where语句解析 -func (that *HoTimeDB) where(data Map) (string, []interface{}) { - - where := "" - - res := make([]interface{}, 0) - //AND OR判断 - testQu := []string{} - //testQuData:= qu[0].(Map) - for key, _ := range data { - //fmt.Println(key, ":", value) - testQu = append(testQu, key) - } - sort.Strings(testQu) - for _, k := range testQu { - v := data[k] - x := 0 - for i := 0; i < len(condition); i++ { - - if condition[i] == k { - - tw, ts := that.cond(k, v.(Map)) - - where += tw - res = append(res, ts...) - break - } - x++ - } - - y := 0 - for j := 0; j < len(vcond); j++ { - - if vcond[j] == k { - break - } - y++ - } - if x == len(condition) && y == len(vcond) { - - if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 { - continue - } - if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 { - continue - } - - tv, vv := that.varCond(k, v) - - where += tv - res = append(res, vv...) - } - - } - - if len(where) != 0 { - hasWhere := true - for _, v := range vcond { - if strings.Index(where, v) == 0 { - hasWhere = false - } - } - - if hasWhere { - where = " WHERE " + where + " " - } - } - - //特殊字符 - for j := 0; j < len(vcond); j++ { - testQu := []string{} - //testQuData:= qu[0].(Map) - for key, _ := range data { - //fmt.Println(key, ":", value) - testQu = append(testQu, key) - } - sort.Strings(testQu) - for _, k := range testQu { - v := data[k] - if vcond[j] == k { - if k == "ORDER" { - where += k + " BY " - //fmt.Println(reflect.ValueOf(v).Type()) - - //break - } else if k == "GROUP" { - - where += k + " BY " - } else { - - where += k - } - - 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++ { - where += " " + vs.GetString(i) + " " - - if len(vs) != i+1 { - where += ", " - } - - } - - } else { - //fmt.Println(v) - where += " " + ObjToStr(v) + " " - } - - break - } - } - - } - - return where, res - -} - -func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) { - - where := "" - res := make([]interface{}, 0) - length := len(k) - if k == "[#]" { - k = strings.Replace(k, "[#]", "", -1) - where += " " + ObjToStr(v) + " " - } else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' { - def := false - - switch Substr(k, length-3, 3) { - case "[>]": - k = strings.Replace(k, "[>]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + ">? " - res = append(res, v) - case "[<]": - k = strings.Replace(k, "[<]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + "b" - - where += " " + ObjToStr(v) - case "[#!]": - k = strings.Replace(k, "[#!]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += " " + k + "!=" + ObjToStr(v) + " " - case "[!#]": - k = strings.Replace(k, "[!#]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += " " + k + "!=" + ObjToStr(v) + " " - case "[~]": - k = strings.Replace(k, "[~]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " LIKE ? " - v = "%" + ObjToStr(v) + "%" - res = append(res, v) - case "[!~]": //左边任意 - k = strings.Replace(k, "[!~]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " LIKE ? " - v = "%" + ObjToStr(v) + "" - res = append(res, v) - case "[~!]": //右边任意 - k = strings.Replace(k, "[~!]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " LIKE ? " - v = ObjToStr(v) + "%" - res = append(res, v) - case "[~~]": //手动任意 - k = strings.Replace(k, "[~]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " LIKE ? " - //v = ObjToStr(v) - res = append(res, v) - default: - def = true - - } - if def { - switch Substr(k, length-4, 4) { - case "[>=]": - k = strings.Replace(k, "[>=]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + ">=? " - res = append(res, v) - case "[<=]": - k = strings.Replace(k, "[<=]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + "<=? " - res = append(res, v) - case "[><]": - k = strings.Replace(k, "[><]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " NOT BETWEEN ? AND ? " - vs := ObjToSlice(v) - res = append(res, vs[0]) - res = append(res, vs[1]) - case "[<>]": - k = strings.Replace(k, "[<>]", "", -1) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - where += k + " BETWEEN ? AND ? " - vs := ObjToSlice(v) - res = append(res, vs[0]) - res = append(res, vs[1]) - default: - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { - vs := ObjToSlice(v) - if len(vs) == 0 { - return where, res - } - - if len(vs) == 1 { - where += k + "=? " - res = append(res, vs[0]) - return where, res - } - - 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 { - //如果就前进一步就用OR - 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 - } - - where += k + " IN (" - res = append(res, vs...) - - for i := 0; i < len(vs); i++ { - if i+1 != len(vs) { - where += "?," - } else { - where += "?) " - } - //res=append(res,(v.(Slice))[i]) - } - - } else { - where += k + "=? " - res = append(res, v) - } - - } - } - - } else { - //fmt.Println(reflect.ValueOf(v).Type().String()) - if !strings.Contains(k, ".") { - k = "`" + k + "` " - } - if v == nil { - where += k + " IS NULL " - } else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { - vs := ObjToSlice(v) - //fmt.Println(v) - if len(vs) == 0 { - return where, res - } - - if len(vs) == 1 { - where += k + "=? " - res = append(res, vs[0]) - return where, res - } - - //In转betwin和IN,提升部分性能 - 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 { - //如果就前进一步就用OR - 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 - } - - where += k + " IN (" - res = append(res, vs...) - - for i := 0; i < len(vs); i++ { - if i+1 != len(vs) { - where += "?," - } else { - where += "?) " - } - //res=append(res,(v.(Slice))[i]) - } - - } else { - - where += k + "=? " - res = append(res, v) - - } - } - - return where, res -} - -// that.Db.Update("user",hotime.Map{"ustate":"1"},hotime.Map{"AND":hotime.Map{"OR":hotime.Map{"uid":4,"uname":"dasda"}},"ustate":1}) -func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) { - //where:="" - //fmt.Println(reflect.ValueOf(v).Type().String()) - 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 += "?) " - } - //res=append(res,(v.(Slice))[i]) - } - - } else { - - where += k + " !=? " - res = append(res, v) - - } - - return where, res -} - -func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) { - where := " " - res := make([]interface{}, 0) - lens := len(data) - //fmt.Println(lens) - testQu := []string{} - //testQuData:= qu[0].(Map) - for key, _ := range data { - //fmt.Println(key, ":", value) - testQu = append(testQu, key) - } - sort.Strings(testQu) - for _, k := range testQu { - v := data[k] - x := 0 - for i := 0; i < len(condition); i++ { - - if condition[i] == k { - - tw, ts := that.cond(k, v.(Map)) - if lens--; lens <= 0 { - //fmt.Println(lens) - where += "(" + tw + ") " - } else { - where += "(" + tw + ") " + tag + " " - } - - res = append(res, ts...) - break - } - x++ - } - - if x == len(condition) { - - tv, vv := 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 -} - -// Update 更新数据 -func (that *HoTimeDB) Update(table string, data Map, where Map) int64 { - - query := "UPDATE `" + that.Prefix + table + "` SET " - //UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson' - qs := make([]interface{}, 0) - tp := len(data) - - for k, v := range data { - vstr := "?" - if Substr(k, len(k)-3, 3) == "[#]" { - k = strings.Replace(k, "[#]", "", -1) - - vstr = ObjToStr(v) - } else { - qs = append(qs, v) - } - query += "`" + k + "`=" + vstr + " " - if tp--; tp != 0 { - query += ", " - } - } - - temp, resWhere := that.where(where) - //fmt.Println(resWhere) - - 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 -} - -func (that *HoTimeDB) Delete(table string, data map[string]interface{}) int64 { - - query := "DELETE FROM `" + that.Prefix + table + "` " - - temp, resWhere := that.where(data) - query += temp + ";" - - res, err := that.Exec(query, resWhere...) - rows := int64(0) - if err.GetError() == nil && res != nil { - rows, _ = res.RowsAffected() - } - - //如果删除成功,删除对应缓存 - if rows != 0 { - if that.HoTimeCache != nil && table != "cached" { - _ = that.HoTimeCache.Db(table+"*", nil) - } - } - //return 0 - - return rows -} - -// Insert 插入新数据 -func (that *HoTimeDB) Insert(table string, data map[string]interface{}) int64 { - - values := make([]interface{}, 0) - queryString := " (" - valueString := " (" - - lens := len(data) - tempLen := 0 - for k, v := range data { - tempLen++ - - vstr := "?" - if Substr(k, len(k)-3, 3) == "[#]" { - k = strings.Replace(k, "[#]", "", -1) - - vstr = ObjToStr(v) - if tempLen < lens { - queryString += "`" + k + "`," - valueString += vstr + "," - } else { - queryString += "`" + k + "`) " - valueString += vstr + ");" - } - } else { - - values = append(values, v) - if tempLen < lens { - queryString += "`" + k + "`," - valueString += "?," - } else { - queryString += "`" + k + "`) " - valueString += "?);" - } - } - - } - query := "INSERT INTO `" + that.Prefix + table + "` " + queryString + "VALUES" + valueString - - res, err := that.Exec(query, values...) - - id := int64(0) - if err.GetError() == nil && res != nil { - id1, err := res.LastInsertId() - that.LastErr.SetError(err) - id = id1 - } - - //如果插入成功,删除缓存 - if id != 0 { - if that.HoTimeCache != nil && table != "cached" { - _ = that.HoTimeCache.Db(table+"*", nil) - } - } - - //fmt.Println(id) - return id -} diff --git a/db/query.go b/db/query.go new file mode 100644 index 0000000..984ce1c --- /dev/null +++ b/db/query.go @@ -0,0 +1,188 @@ +package db + +import ( + . "code.hoteas.com/golang/hotime/common" + "database/sql" + "encoding/json" + "errors" + "reflect" + "strings" +) + +// md5 生成查询的 MD5 哈希(用于缓存) +func (that *HoTimeDB) md5(query string, args ...interface{}) string { + strByte, _ := json.Marshal(args) + str := Md5(query + ":" + string(strByte)) + return str +} + +// Query 执行查询 SQL +func (that *HoTimeDB) Query(query string, args ...interface{}) []Map { + return that.queryWithRetry(query, false, args...) +} + +// queryWithRetry 内部查询方法,支持重试标记 +func (that *HoTimeDB) queryWithRetry(query string, retried bool, args ...interface{}) []Map { + // 保存调试信息(加锁保护) + that.mu.Lock() + that.LastQuery = query + that.LastData = args + that.mu.Unlock() + + defer func() { + if that.Mode != 0 { + that.mu.RLock() + that.Log.Info("SQL:"+that.LastQuery, " DATA:", that.LastData, " ERROR:", that.LastErr.GetError()) + that.mu.RUnlock() + } + }() + + var err error + var resl *sql.Rows + + // 主从数据库切换,只有select语句有从数据库 + db := that.DB + if that.SlaveDB != nil { + db = that.SlaveDB + } + + if db == nil { + err = errors.New("没有初始化数据库") + that.LastErr.SetError(err) + return nil + } + + // 处理参数中的 slice 类型 + processedArgs := that.processArgs(args) + + if that.Tx != nil { + resl, err = that.Tx.Query(query, processedArgs...) + } else { + resl, err = db.Query(query, processedArgs...) + } + + that.LastErr.SetError(err) + if err != nil { + // 如果还没重试过,尝试 Ping 后重试一次 + if !retried { + if pingErr := db.Ping(); pingErr == nil { + return that.queryWithRetry(query, true, args...) + } + } + return nil + } + + return that.Row(resl) +} + +// Exec 执行非查询 SQL +func (that *HoTimeDB) Exec(query string, args ...interface{}) (sql.Result, *Error) { + return that.execWithRetry(query, false, args...) +} + +// execWithRetry 内部执行方法,支持重试标记 +func (that *HoTimeDB) execWithRetry(query string, retried bool, args ...interface{}) (sql.Result, *Error) { + // 保存调试信息(加锁保护) + that.mu.Lock() + that.LastQuery = query + that.LastData = args + that.mu.Unlock() + + defer func() { + if that.Mode != 0 { + that.mu.RLock() + that.Log.Info("SQL: "+that.LastQuery, " DATA: ", that.LastData, " ERROR: ", that.LastErr.GetError()) + that.mu.RUnlock() + } + }() + + var e error + var resl sql.Result + + if that.DB == nil { + err := errors.New("没有初始化数据库") + that.LastErr.SetError(err) + return nil, that.LastErr + } + + // 处理参数中的 slice 类型 + processedArgs := that.processArgs(args) + + if that.Tx != nil { + resl, e = that.Tx.Exec(query, processedArgs...) + } else { + resl, e = that.DB.Exec(query, processedArgs...) + } + + that.LastErr.SetError(e) + // 判断是否连接断开了,如果还没重试过,尝试重试一次 + if e != nil { + if !retried { + if pingErr := that.DB.Ping(); pingErr == nil { + return that.execWithRetry(query, true, args...) + } + } + return resl, that.LastErr + } + + return resl, that.LastErr +} + +// processArgs 处理参数中的 slice 类型 +func (that *HoTimeDB) processArgs(args []interface{}) []interface{} { + processedArgs := make([]interface{}, len(args)) + copy(processedArgs, args) + for key := range processedArgs { + arg := processedArgs[key] + if arg == nil { + continue + } + argType := reflect.ValueOf(arg).Type().String() + if strings.Contains(argType, "[]") || strings.Contains(argType, "Slice") { + argLis := ObjToSlice(arg) + // 将slice转为逗号分割字符串 + argStr := "" + for i := 0; i < len(argLis); i++ { + if i == len(argLis)-1 { + argStr += ObjToStr(argLis[i]) + } else { + argStr += ObjToStr(argLis[i]) + "," + } + } + processedArgs[key] = argStr + } + } + return processedArgs +} + +// Row 数据库数据解析 +func (that *HoTimeDB) Row(resl *sql.Rows) []Map { + dest := make([]Map, 0) + strs, _ := resl.Columns() + + for i := 0; resl.Next(); i++ { + lis := make(Map, 0) + a := make([]interface{}, len(strs)) + + b := make([]interface{}, len(a)) + for j := 0; j < len(a); j++ { + b[j] = &a[j] + } + err := resl.Scan(b...) + if err != nil { + that.LastErr.SetError(err) + return nil + } + for j := 0; j < len(a); j++ { + if a[j] != nil && reflect.ValueOf(a[j]).Type().String() == "[]uint8" { + lis[strs[j]] = string(a[j].([]byte)) + } else { + lis[strs[j]] = a[j] // 取实际类型 + } + } + + dest = append(dest, lis) + } + + return dest +} diff --git a/db/transaction.go b/db/transaction.go new file mode 100644 index 0000000..c9beb65 --- /dev/null +++ b/db/transaction.go @@ -0,0 +1,57 @@ +package db + +import ( + "sync" +) + +// Action 事务操作 +// 如果 action 返回 true 则提交事务;返回 false 则回滚 +func (that *HoTimeDB) Action(action func(db HoTimeDB) (isSuccess bool)) (isSuccess bool) { + db := HoTimeDB{ + DB: that.DB, + ContextBase: that.ContextBase, + DBName: that.DBName, + HoTimeCache: that.HoTimeCache, + Log: that.Log, + Type: that.Type, + Prefix: that.Prefix, + LastQuery: that.LastQuery, + LastData: that.LastData, + ConnectFunc: that.ConnectFunc, + LastErr: that.LastErr, + limit: that.limit, + Tx: that.Tx, + SlaveDB: that.SlaveDB, + Mode: that.Mode, + Dialect: that.Dialect, + mu: sync.RWMutex{}, + limitMu: sync.Mutex{}, + } + + tx, err := db.Begin() + + if err != nil { + that.LastErr.SetError(err) + return isSuccess + } + + db.Tx = tx + + isSuccess = action(db) + + if !isSuccess { + err = db.Tx.Rollback() + if err != nil { + that.LastErr.SetError(err) + return isSuccess + } + return isSuccess + } + + err = db.Tx.Commit() + if err != nil { + that.LastErr.SetError(err) + return false + } + return true +} diff --git a/db/where.go b/db/where.go new file mode 100644 index 0000000..917ef6e --- /dev/null +++ b/db/where.go @@ -0,0 +1,545 @@ +package db + +import ( + . "code.hoteas.com/golang/hotime/common" + "reflect" + "sort" + "strings" +) + +// 条件关键字 +var condition = []string{"AND", "OR"} + +// 特殊关键字(支持大小写) +var vcond = []string{"GROUP", "ORDER", "LIMIT", "DISTINCT", "HAVING", "OFFSET"} + +// normalizeKey 标准化关键字(转大写) +func normalizeKey(k string) string { + upper := strings.ToUpper(k) + for _, v := range vcond { + if upper == v { + return v + } + } + for _, v := range condition { + if upper == v { + return v + } + } + return k +} + +// isConditionKey 判断是否是条件关键字 +func isConditionKey(k string) bool { + upper := strings.ToUpper(k) + for _, v := range condition { + if upper == v { + return true + } + } + return false +} + +// isVcondKey 判断是否是特殊关键字 +func isVcondKey(k string) bool { + upper := strings.ToUpper(k) + for _, v := range vcond { + if upper == v { + return true + } + } + return false +} + +// where 语句解析 +func (that *HoTimeDB) where(data Map) (string, []interface{}) { + where := "" + res := make([]interface{}, 0) + + // 标准化 Map 的 key(大小写兼容) + normalizedData := Map{} + for k, v := range data { + normalizedData[normalizeKey(k)] = v + } + data = normalizedData + + // 收集所有 key 并排序 + testQu := []string{} + for key := range data { + testQu = append(testQu, key) + } + sort.Strings(testQu) + + // 追踪普通条件数量,用于自动添加 AND + normalCondCount := 0 + + for _, k := range testQu { + v := data[k] + + // 检查是否是 AND/OR 条件关键字 + if isConditionKey(k) { + tw, ts := that.cond(strings.ToUpper(k), v.(Map)) + where += tw + res = append(res, ts...) + continue + } + + // 检查是否是特殊关键字(GROUP, ORDER, LIMIT 等) + if isVcondKey(k) { + continue // 特殊关键字在后面单独处理 + } + + // 处理普通条件字段 + if v != nil && reflect.ValueOf(v).Type().String() == "common.Slice" && len(v.(Slice)) == 0 { + continue + } + if v != nil && strings.Contains(reflect.ValueOf(v).Type().String(), "[]") && len(ObjToSlice(v)) == 0 { + continue + } + + tv, vv := that.varCond(k, v) + if tv != "" { + // 自动添加 AND 连接符 + if normalCondCount > 0 { + where += " AND " + } + where += tv + normalCondCount++ + res = append(res, vv...) + } + } + + // 添加 WHERE 关键字 + if len(where) != 0 { + hasWhere := true + for _, v := range vcond { + if strings.Index(where, v) == 0 { + hasWhere = false + } + } + + if hasWhere { + where = " WHERE " + where + " " + } + } + + // 处理特殊字符(按固定顺序:GROUP, HAVING, ORDER, LIMIT, OFFSET) + specialOrder := []string{"GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "DISTINCT"} + for _, vcondKey := range specialOrder { + v, exists := data[vcondKey] + if !exists { + continue + } + + switch vcondKey { + case "GROUP": + where += " GROUP BY " + where += that.formatVcondValue(v) + case "HAVING": + // HAVING 条件处理 + if havingMap, ok := v.(Map); ok { + havingWhere, havingRes := that.cond("AND", havingMap) + if havingWhere != "" { + where += " HAVING " + strings.TrimSpace(havingWhere) + " " + res = append(res, havingRes...) + } + } + case "ORDER": + where += " ORDER BY " + where += that.formatVcondValue(v) + case "LIMIT": + where += " LIMIT " + where += that.formatVcondValue(v) + case "OFFSET": + where += " OFFSET " + ObjToStr(v) + " " + case "DISTINCT": + // DISTINCT 通常在 SELECT 中处理,这里暂时忽略 + } + } + + return where, res +} + +// formatVcondValue 格式化特殊关键字的值 +func (that *HoTimeDB) formatVcondValue(v interface{}) string { + result := "" + if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { + vs := ObjToSlice(v) + for i := 0; i < len(vs); i++ { + result += " " + vs.GetString(i) + " " + if len(vs) != i+1 { + result += ", " + } + } + } else { + result += " " + ObjToStr(v) + " " + } + return result +} + +// varCond 变量条件解析 +func (that *HoTimeDB) varCond(k string, v interface{}) (string, []interface{}) { + where := "" + res := make([]interface{}, 0) + length := len(k) + + if k == "[#]" { + k = strings.Replace(k, "[#]", "", -1) + where += " " + ObjToStr(v) + " " + } else if length > 0 && strings.Contains(k, "[") && k[length-1] == ']' { + def := false + + switch Substr(k, length-3, 3) { + case "[>]": + k = strings.Replace(k, "[>]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + ">? " + res = append(res, v) + case "[<]": + k = strings.Replace(k, "[<]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + "=]": + k = strings.Replace(k, "[>=]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + ">=? " + res = append(res, v) + case "[<=]": + k = strings.Replace(k, "[<=]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + "<=? " + res = append(res, v) + case "[><]": + k = strings.Replace(k, "[><]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + " NOT BETWEEN ? AND ? " + vs := ObjToSlice(v) + res = append(res, vs[0]) + res = append(res, vs[1]) + case "[<>]": + k = strings.Replace(k, "[<>]", "", -1) + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + where += k + " BETWEEN ? AND ? " + vs := ObjToSlice(v) + res = append(res, vs[0]) + res = append(res, vs[1]) + default: + where, res = that.handleDefaultCondition(k, v, where, res) + } + } + } else { + where, res = that.handlePlainField(k, v, where, res) + } + + return where, res +} + +// handleDefaultCondition 处理默认条件(带方括号但不是特殊操作符) +func (that *HoTimeDB) handleDefaultCondition(k string, v interface{}, where string, res []interface{}) (string, []interface{}) { + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + + if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { + vs := ObjToSlice(v) + if len(vs) == 0 { + return where, res + } + + if len(vs) == 1 { + where += k + "=? " + res = append(res, vs[0]) + return where, res + } + + // IN 优化:连续整数转为 BETWEEN + where, res = that.optimizeInCondition(k, vs, where, res) + } else { + where += k + "=? " + res = append(res, v) + } + + return where, res +} + +// handlePlainField 处理普通字段(无方括号) +func (that *HoTimeDB) handlePlainField(k string, v interface{}, where string, res []interface{}) (string, []interface{}) { + if !strings.Contains(k, ".") { + k = "`" + k + "` " + } + + if v == nil { + where += k + " IS NULL " + } else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { + vs := ObjToSlice(v) + if len(vs) == 0 { + return where, res + } + + if len(vs) == 1 { + where += k + "=? " + res = append(res, vs[0]) + return where, res + } + + // IN 优化 + where, res = that.optimizeInCondition(k, vs, where, res) + } else { + where += k + "=? " + res = append(res, v) + } + + return where, res +} + +// optimizeInCondition 优化 IN 条件(连续整数转为 BETWEEN) +func (that *HoTimeDB) optimizeInCondition(k string, vs Slice, where string, res []interface{}) (string, []interface{}) { + min := int64(0) + isMin := true + IsRange := true + num := int64(0) + isNum := true + + where1 := "" + res1 := Slice{} + where2 := k + " IN (" + res2 := Slice{} + + for kvs := 0; kvs <= len(vs); kvs++ { + vsv := int64(0) + if kvs < len(vs) { + vsv = vs.GetCeilInt64(kvs) + // 确保是全部是int类型 + if ObjToStr(vsv) != vs.GetString(kvs) { + IsRange = false + break + } + } + + if isNum { + isNum = false + num = vsv + } else { + num++ + } + if isMin { + isMin = false + min = vsv + } + // 不等于则到了分路口 + if num != vsv { + // between + if num-min > 1 { + if where1 != "" { + where1 += " OR " + k + " BETWEEN ? AND ? " + } else { + where1 += k + " BETWEEN ? AND ? " + } + res1 = append(res1, min) + res1 = append(res1, num-1) + } else { + where2 += "?," + res2 = append(res2, min) + } + min = vsv + num = vsv + } + } + + if IsRange { + where3 := "" + + if where1 != "" { + where3 += where1 + res = append(res, res1...) + } + + if len(res2) == 1 { + if where3 == "" { + where3 += k + " = ? " + } else { + where3 += " OR " + k + " = ? " + } + res = append(res, res2...) + } else if len(res2) > 1 { + where2 = where2[:len(where2)-1] + if where3 == "" { + where3 += where2 + ")" + } else { + where3 += " OR " + where2 + ")" + } + res = append(res, res2...) + } + + if where3 != "" { + where += "(" + where3 + ")" + } + + return where, res + } + + // 非连续整数,使用普通 IN + where += k + " IN (" + res = append(res, vs...) + + for i := 0; i < len(vs); i++ { + if i+1 != len(vs) { + where += "?," + } else { + where += "?) " + } + } + + return where, res +} + +// notIn NOT IN 条件处理 +func (that *HoTimeDB) notIn(k string, v interface{}, where string, res []interface{}) (string, []interface{}) { + if v == nil { + where += k + " IS NOT NULL " + } else if reflect.ValueOf(v).Type().String() == "common.Slice" || strings.Contains(reflect.ValueOf(v).Type().String(), "[]") { + vs := ObjToSlice(v) + if len(vs) == 0 { + return where, res + } + where += k + " NOT IN (" + res = append(res, vs...) + + for i := 0; i < len(vs); i++ { + if i+1 != len(vs) { + where += "?," + } else { + where += "?) " + } + } + } else { + where += k + " !=? " + res = append(res, v) + } + + return where, res +} + +// cond 条件组合处理 +func (that *HoTimeDB) cond(tag string, data Map) (string, []interface{}) { + where := " " + res := make([]interface{}, 0) + lens := len(data) + + testQu := []string{} + for key := range data { + testQu = append(testQu, key) + } + sort.Strings(testQu) + + for _, k := range testQu { + v := data[k] + x := 0 + for i := 0; i < len(condition); i++ { + if condition[i] == strings.ToUpper(k) { + tw, ts := that.cond(strings.ToUpper(k), v.(Map)) + if lens--; lens <= 0 { + where += "(" + tw + ") " + } else { + where += "(" + tw + ") " + tag + " " + } + + res = append(res, ts...) + break + } + x++ + } + + if x == len(condition) { + tv, vv := that.varCond(k, v) + if tv == "" { + lens-- + continue + } + res = append(res, vv...) + if lens--; lens <= 0 { + where += tv + "" + } else { + where += tv + " " + tag + " " + } + } + } + + return where, res +} diff --git a/example/config/adminDB.json b/example/config/adminDB.json index a922afc..4f3c953 100644 --- a/example/config/adminDB.json +++ b/example/config/adminDB.json @@ -74,7 +74,7 @@ "show" ], "icon": "Setting", - "label": "栏目管理", + "label": "系统管理", "menus": [ { "auth": [ @@ -85,8 +85,8 @@ "info", "download" ], - "label": "栏目管理", - "table": "ctg" + "label": "人员管理", + "table": "admin" }, { "auth": [ @@ -97,8 +97,8 @@ "info", "download" ], - "label": "栏目管理", - "table": "ctg_copy" + "label": "角色管理", + "table": "role" }, { "auth": [ @@ -109,103 +109,39 @@ "info", "download" ], - "label": "关联栏目", - "table": "ctg_article" + "label": "文章管理", + "table": "article" + }, + { + "auth": [ + "show", + "download" + ], + "label": "日志管理", + "table": "logs" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "组织管理", + "table": "org" } ], - "name": "sys:ctg" + "name": "sys" }, { "auth": [ "show" ], "icon": "Setting", - "label": "ebw_vote", + "label": "ebw_attachment", "menus": [ - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_vote", - "table": "ebw_vote" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_vote_user", - "table": "ebw_vote_user" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_vote_option", - "table": "ebw_vote_option" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_annex", - "table": "ebw_annex" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_customer", - "table": "ebw_customer" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_user", - "table": "ebw_user" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "ebw_res", - "table": "ebw_res" - }, { "auth": [ "show", @@ -218,6 +154,18 @@ "label": "ebw_attachment", "table": "ebw_attachment" }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_jobs", + "table": "ebw_jobs" + }, { "auth": [ "show", @@ -242,6 +190,30 @@ "label": "ebw_news_addition_res", "table": "ebw_news_addition_res" }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_annex", + "table": "ebw_annex" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_res", + "table": "ebw_res" + }, { "auth": [ "show", @@ -263,8 +235,56 @@ "info", "download" ], - "label": "ebw_jobs", - "table": "ebw_jobs" + "label": "ebw_vote", + "table": "ebw_vote" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_customer", + "table": "ebw_customer" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_user", + "table": "ebw_user" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_vote_option", + "table": "ebw_vote_option" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "ebw_vote_user", + "table": "ebw_vote_user" } ], "name": "sys:ebw" @@ -297,8 +317,8 @@ "info", "download" ], - "label": "总经理信箱", - "table": "mail" + "label": "党委书记信箱", + "table": "mail_part" }, { "auth": [ @@ -309,8 +329,8 @@ "info", "download" ], - "label": "党委书记信箱", - "table": "mail_part" + "label": "总经理信箱", + "table": "mail" } ], "name": "sys:mail" @@ -320,44 +340,8 @@ "show" ], "icon": "Setting", - "label": "顶部", + "label": "底部", "menus": [ - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "顶部", - "table": "swiper_top" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "中间", - "table": "swiper_center" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "外部系统", - "table": "swiper_sys" - }, { "auth": [ "show", @@ -382,6 +366,18 @@ "label": "关联专题", "table": "swiper_point" }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "中间", + "table": "swiper_center" + }, { "auth": [ "show", @@ -393,6 +389,30 @@ ], "label": "飘窗", "table": "swiper_fly" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "外部系统", + "table": "swiper_sys" + }, + { + "auth": [ + "show", + "add", + "delete", + "edit", + "info", + "download" + ], + "label": "顶部", + "table": "swiper_top" } ], "name": "sys:swiper" @@ -402,7 +422,7 @@ "show" ], "icon": "Setting", - "label": "系统管理", + "label": "栏目管理", "menus": [ { "auth": [ @@ -413,8 +433,8 @@ "info", "download" ], - "label": "人员管理", - "table": "admin" + "label": "栏目管理", + "table": "ctg_copy" }, { "auth": [ @@ -425,16 +445,8 @@ "info", "download" ], - "label": "文章管理", - "table": "article" - }, - { - "auth": [ - "show", - "download" - ], - "label": "日志管理", - "table": "logs" + "label": "关联栏目", + "table": "ctg_article" }, { "auth": [ @@ -445,23 +457,11 @@ "info", "download" ], - "label": "组织管理", - "table": "org" - }, - { - "auth": [ - "show", - "add", - "delete", - "edit", - "info", - "download" - ], - "label": "角色管理", - "table": "role" + "label": "栏目管理", + "table": "ctg" } ], - "name": "sys" + "name": "sys:ctg" } ], "name": "admin", @@ -574,14 +574,14 @@ "name": "ID", "value": "id" }, - { - "name": "名称", - "value": "name" - }, { "name": "职位", "value": "title" }, + { + "name": "名称", + "value": "name" + }, { "name": "手机号", "value": "phone" @@ -797,20 +797,20 @@ "value": "title" }, { - "name": "来源", - "value": "origin" + "name": "作者", + "value": "author" }, { - "name": "描述", - "value": "description" + "name": "来源", + "value": "origin" }, { "name": "id", "value": "id" }, { - "name": "作者", - "value": "author" + "name": "描述", + "value": "description" }, { "name": "正文", @@ -832,14 +832,14 @@ "name": "无", "value": null }, - { - "name": "创建时间", - "value": "create_time" - }, { "name": "推送时间", "value": "push_time" }, + { + "name": "创建时间", + "value": "create_time" + }, { "name": "变更时间", "value": "modify_time" @@ -1028,12 +1028,12 @@ "value": null }, { - "name": "编码", - "value": "sn" + "name": "板块名称", + "value": "name" }, { - "name": "源链接", - "value": "url" + "name": "编码", + "value": "sn" }, { "name": "文章", @@ -1044,8 +1044,8 @@ "value": "id" }, { - "name": "板块名称", - "value": "name" + "name": "源链接", + "value": "url" } ], "type": "search", @@ -1215,10 +1215,6 @@ "name": "无", "value": null }, - { - "name": "编号", - "value": "sn" - }, { "name": "文章", "value": "article_id" @@ -1226,6 +1222,10 @@ { "name": "id", "value": "id" + }, + { + "name": "编号", + "value": "sn" } ], "type": "search", @@ -1430,10 +1430,18 @@ "name": "无", "value": null }, + { + "name": "编码", + "value": "sn" + }, { "name": "源链接", "value": "url" }, + { + "name": "文章", + "value": "article_id" + }, { "name": "ID", "value": "id" @@ -1441,14 +1449,6 @@ { "name": "板块名称", "value": "name" - }, - { - "name": "编码", - "value": "sn" - }, - { - "name": "文章", - "value": "article_id" } ], "type": "search", @@ -2483,18 +2483,14 @@ "name": "无", "value": null }, - { - "name": "id", - "value": "id" - }, - { - "name": "no", - "value": "no" - }, { "name": "name", "value": "name" }, + { + "name": "pass", + "value": "pass" + }, { "name": "mpass", "value": "mpass" @@ -2504,32 +2500,36 @@ "value": "adrress" }, { - "name": "mail", - "value": "mail" + "name": "tel", + "value": "tel" }, { - "name": "type", - "value": "type" + "name": "mail", + "value": "mail" }, { "name": "depNo", "value": "depNo" }, + { + "name": "id", + "value": "id" + }, { "name": "account", "value": "account" }, { - "name": "pass", - "value": "pass" - }, - { - "name": "tel", - "value": "tel" + "name": "type", + "value": "type" }, { "name": "depName", "value": "depName" + }, + { + "name": "no", + "value": "no" } ], "type": "search", @@ -2976,13 +2976,13 @@ "name": "ID", "value": "id" }, - { - "name": "IP地址", - "value": "ip" - }, { "name": "其他内容", "value": "name" + }, + { + "name": "IP地址", + "value": "ip" } ], "type": "search", @@ -3022,30 +3022,6 @@ "type": "search", "value": null }, - { - "label": "状态", - "name": "state", - "options": [ - { - "name": "已启用", - "value": "0" - }, - { - "name": "未启用", - "value": "1" - }, - { - "name": "异常", - "value": "2" - }, - { - "name": "全部", - "value": null - } - ], - "type": "select", - "value": null - }, { "default": "0", "label": "操作类型", @@ -3075,6 +3051,30 @@ "sortable": true, "type": "select", "value": null + }, + { + "label": "状态", + "name": "state", + "options": [ + { + "name": "已启用", + "value": "0" + }, + { + "name": "未启用", + "value": "1" + }, + { + "name": "异常", + "value": "2" + }, + { + "name": "全部", + "value": null + } + ], + "type": "select", + "value": null } ], "table": "logs" @@ -3210,13 +3210,17 @@ "value": null }, { - "name": "姓名", - "value": "name" + "name": "标题", + "value": "title" }, { "name": "手机号", "value": "phone" }, + { + "name": "IP", + "value": "not_show_ip" + }, { "name": "id", "value": "id" @@ -3225,17 +3229,13 @@ "name": "编号", "value": "sn" }, - { - "name": "标题", - "value": "title" - }, { "name": "内容", "value": "content" }, { - "name": "IP", - "value": "not_show_ip" + "name": "姓名", + "value": "name" } ], "type": "search", @@ -3279,6 +3279,27 @@ "type": "search", "value": null }, + { + "default": "0", + "label": "状态", + "name": "state", + "options": [ + { + "name": "已启用", + "value": "0" + }, + { + "name": "未启用", + "value": "1" + }, + { + "name": "全部", + "value": null + } + ], + "type": "select", + "value": null + }, { "default": "0", "label": "是否展示", @@ -3301,27 +3322,6 @@ "type": "select", "value": null }, - { - "default": "0", - "label": "状态", - "name": "state", - "options": [ - { - "name": "已启用", - "value": "0" - }, - { - "name": "未启用", - "value": "1" - }, - { - "name": "全部", - "value": null - } - ], - "type": "select", - "value": null - }, { "default": "0", "label": "进展", @@ -3484,6 +3484,14 @@ "name": "id", "value": "id" }, + { + "name": "手机号", + "value": "phone" + }, + { + "name": "编号", + "value": "sn" + }, { "name": "标题", "value": "title" @@ -3492,18 +3500,10 @@ "name": "内容", "value": "content" }, - { - "name": "编号", - "value": "sn" - }, { "name": "姓名", "value": "name" }, - { - "name": "手机号", - "value": "phone" - }, { "name": "IP", "value": "not_show_ip" @@ -3520,13 +3520,13 @@ "name": "无", "value": null }, - { - "name": "创建时间", - "value": "create_time" - }, { "name": "变更时间", "value": "modify_time" + }, + { + "name": "创建时间", + "value": "create_time" } ], "type": "search", @@ -3571,28 +3571,6 @@ "type": "select", "value": null }, - { - "default": "0", - "label": "是否展示", - "name": "show", - "options": [ - { - "name": "否", - "value": "0" - }, - { - "name": "是", - "value": "1" - }, - { - "name": "全部", - "value": null - } - ], - "sortable": true, - "type": "select", - "value": null - }, { "default": "0", "label": "进展", @@ -3617,6 +3595,28 @@ ], "type": "select", "value": null + }, + { + "default": "0", + "label": "是否展示", + "name": "show", + "options": [ + { + "name": "否", + "value": "0" + }, + { + "name": "是", + "value": "1" + }, + { + "name": "全部", + "value": null + } + ], + "sortable": true, + "type": "select", + "value": null } ], "table": "mail_discipline" @@ -3752,12 +3752,12 @@ "value": null }, { - "name": "IP", - "value": "not_show_ip" + "name": "id", + "value": "id" }, { - "name": "标题", - "value": "title" + "name": "编号", + "value": "sn" }, { "name": "内容", @@ -3768,16 +3768,16 @@ "value": "phone" }, { - "name": "id", - "value": "id" - }, - { - "name": "编号", - "value": "sn" + "name": "标题", + "value": "title" }, { "name": "姓名", "value": "name" + }, + { + "name": "IP", + "value": "not_show_ip" } ], "type": "search", @@ -3821,6 +3821,27 @@ "type": "search", "value": null }, + { + "default": "0", + "label": "状态", + "name": "state", + "options": [ + { + "name": "已启用", + "value": "0" + }, + { + "name": "未启用", + "value": "1" + }, + { + "name": "全部", + "value": null + } + ], + "type": "select", + "value": null + }, { "default": "0", "label": "进展", @@ -3846,27 +3867,6 @@ "type": "select", "value": null }, - { - "default": "0", - "label": "状态", - "name": "state", - "options": [ - { - "name": "已启用", - "value": "0" - }, - { - "name": "未启用", - "value": "1" - }, - { - "name": "全部", - "value": null - } - ], - "type": "select", - "value": null - }, { "default": "0", "label": "是否展示", @@ -4007,13 +4007,13 @@ "name": "ID", "value": "id" }, - { - "name": "板块名称", - "value": "name" - }, { "name": "编码", "value": "sn" + }, + { + "name": "板块名称", + "value": "name" } ], "type": "search", @@ -4657,16 +4657,16 @@ "value": null }, { - "name": "名称", - "value": "name" + "name": "ID", + "value": "id" }, { "name": "链接", "value": "url" }, { - "name": "ID", - "value": "id" + "name": "名称", + "value": "name" } ], "type": "search", @@ -5034,13 +5034,13 @@ "name": "无", "value": null }, - { - "name": "变更时间", - "value": "modify_time" - }, { "name": "创建时间", "value": "create_time" + }, + { + "name": "变更时间", + "value": "modify_time" } ], "type": "search", diff --git a/example/zctv2.exe b/example/zctv2.exe index 8d3ce31..15f80f9 100644 Binary files a/example/zctv2.exe and b/example/zctv2.exe differ diff --git a/examples/hotimedb_examples.go b/examples/hotimedb_examples.go index d540150..dfbb4bd 100644 --- a/examples/hotimedb_examples.go +++ b/examples/hotimedb_examples.go @@ -372,7 +372,7 @@ func Example7_AggregateQuery_Fixed(db *db.HoTimeDB) { } // 示例8: 事务处理(修正版) -func Example8_Transaction_Fixed(db *db.HoTimeDB) { +func Example8_Transaction_Fixed(database *db.HoTimeDB) { fmt.Println("=== 事务处理示例 ===") // 模拟转账操作 @@ -380,7 +380,7 @@ func Example8_Transaction_Fixed(db *db.HoTimeDB) { toUserId := int64(2) amount := 100.0 - success := db.Action(func(tx db.HoTimeDB) bool { + success := database.Action(func(tx db.HoTimeDB) bool { // 检查转出账户余额(单条件) fromUser := tx.Get("user", "balance", common.Map{"id": fromUserId}) if fromUser == nil { @@ -445,22 +445,22 @@ func Example8_Transaction_Fixed(db *db.HoTimeDB) { fmt.Println("事务执行成功") } else { fmt.Println("事务回滚") - if db.LastErr.GetError() != nil { - fmt.Println("错误原因:", db.LastErr.GetError()) + if database.LastErr.GetError() != nil { + fmt.Println("错误原因:", database.LastErr.GetError()) } } } // 示例9: 缓存机制(修正版) -func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { +func Example9_CacheSystem_Fixed(database *db.HoTimeDB) { fmt.Println("=== 缓存机制示例 ===") // 设置缓存(实际项目中需要配置缓存参数) - db.HoTimeCache = &cache.HoTimeCache{} + database.HoTimeCache = &cache.HoTimeCache{} // 第一次查询(会缓存结果) fmt.Println("第一次查询(会缓存)...") - users1 := db.Select("user", "*", common.Map{ + users1 := database.Select("user", "*", common.Map{ "status": 1, // 单条件 "LIMIT": 10, }) @@ -468,7 +468,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { // 第二次相同查询(从缓存获取) fmt.Println("第二次相同查询(从缓存获取)...") - users2 := db.Select("user", "*", common.Map{ + users2 := database.Select("user", "*", common.Map{ "status": 1, // 单条件 "LIMIT": 10, }) @@ -476,7 +476,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { // 更新操作会清除缓存 fmt.Println("执行更新操作(会清除缓存)...") - affected := db.Update("user", common.Map{ + affected := database.Update("user", common.Map{ "updated_time[#]": "NOW()", }, common.Map{ "id": 1, // 单条件 @@ -485,7 +485,7 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { // 再次查询(重新从数据库获取并缓存) fmt.Println("更新后再次查询(重新缓存)...") - users3 := db.Select("user", "*", common.Map{ + users3 := database.Select("user", "*", common.Map{ "status": 1, // 单条件 "LIMIT": 10, }) @@ -493,20 +493,20 @@ func Example9_CacheSystem_Fixed(db *db.HoTimeDB) { } // 示例10: 性能优化技巧(修正版) -func Example10_PerformanceOptimization_Fixed(db *db.HoTimeDB) { +func Example10_PerformanceOptimization_Fixed(database *db.HoTimeDB) { fmt.Println("=== 性能优化技巧示例 ===") // IN查询优化(连续数字自动转为BETWEEN) fmt.Println("IN查询优化示例...") - users := db.Select("user", "*", common.Map{ + users := database.Select("user", "*", common.Map{ "id": []int{1, 2, 3, 4, 5, 10, 11, 12, 13, 20}, // 单个IN条件,会被优化 }) fmt.Printf("查询到 %d 个用户\n", len(users)) - fmt.Println("执行的SQL:", db.LastQuery) + fmt.Println("执行的SQL:", database.LastQuery) // 批量插入(使用事务) fmt.Println("\n批量插入示例...") - success := db.Action(func(tx db.HoTimeDB) bool { + success := database.Action(func(tx db.HoTimeDB) bool { for i := 1; i <= 100; i++ { id := tx.Insert("user_batch", common.Map{ "name": fmt.Sprintf("批量用户%d", i), @@ -534,7 +534,7 @@ func Example10_PerformanceOptimization_Fixed(db *db.HoTimeDB) { // 索引友好的查询(修正版) fmt.Println("\n索引友好的查询...") - recentUsers := db.Select("user", "*", common.Map{ + recentUsers := database.Select("user", "*", common.Map{ "AND": common.Map{ "created_time[>]": "2023-01-01", // 假设created_time有索引 "status": 1, // 假设status有索引 @@ -766,6 +766,66 @@ correctUsers4 := db.Select("user", "*", common.Map{ _ = db } +// BatchInsertExample 批量插入示例 +func BatchInsertExample(database *db.HoTimeDB) { + fmt.Println("\n=== 批量插入示例 ===") + + // 批量插入用户(使用 []Map 格式) + affected := database.BatchInsert("user", []common.Map{ + {"name": "批量用户1", "email": "batch1@example.com", "age": 25, "status": 1}, + {"name": "批量用户2", "email": "batch2@example.com", "age": 30, "status": 1}, + {"name": "批量用户3", "email": "batch3@example.com", "age": 28, "status": 1}, + }) + fmt.Printf("批量插入了 %d 条用户记录\n", affected) + + // 批量插入日志(使用 [#] 标记直接 SQL) + logAffected := database.BatchInsert("log", []common.Map{ + {"user_id": 1, "action": "login", "ip": "192.168.1.1", "created_time[#]": "NOW()"}, + {"user_id": 2, "action": "logout", "ip": "192.168.1.2", "created_time[#]": "NOW()"}, + {"user_id": 3, "action": "view", "ip": "192.168.1.3", "created_time[#]": "NOW()"}, + }) + fmt.Printf("批量插入了 %d 条日志记录\n", logAffected) +} + +// UpsertExample Upsert(插入或更新)示例 +func UpsertExample(database *db.HoTimeDB) { + fmt.Println("\n=== Upsert示例 ===") + + // Upsert 用户数据(使用 Slice 格式) + // 如果 id 存在则更新,不存在则插入 + affected := database.Upsert("user", + common.Map{ + "id": 1, + "name": "张三更新", + "email": "zhang_updated@example.com", + "age": 26, + }, + common.Slice{"id"}, // 唯一键 + common.Slice{"name", "email", "age"}, // 冲突时更新的字段 + ) + fmt.Printf("Upsert 影响了 %d 行\n", affected) + + // Upsert 使用 [#] 直接 SQL + affected2 := database.Upsert("user_stats", + common.Map{ + "user_id": 1, + "login_count[#]": "login_count + 1", + "last_login[#]": "NOW()", + }, + common.Slice{"user_id"}, + common.Slice{"login_count", "last_login"}, + ) + fmt.Printf("统计 Upsert 影响了 %d 行\n", affected2) + + // 也支持可变参数形式 + affected3 := database.Upsert("user", + common.Map{"id": 2, "name": "李四", "status": 1}, + common.Slice{"id"}, + "name", "status", // 可变参数 + ) + fmt.Printf("可变参数 Upsert 影响了 %d 行\n", affected3) +} + // 运行所有修正后的示例 func RunAllFixedExamples() { fmt.Println("开始运行HoTimeDB所有修正后的示例...") @@ -776,6 +836,10 @@ func RunAllFixedExamples() { fmt.Println("请根据实际环境配置数据库连接后运行相应示例") fmt.Println("所有示例代码已修正完毕,语法正确!") + fmt.Println("") + fmt.Println("新增功能示例说明:") + fmt.Println(" - BatchInsertExample(db): 批量插入示例,使用 []Map 格式") + fmt.Println(" - UpsertExample(db): 插入或更新示例") } func main() {