Compare commits
No commits in common. "master" and "v1.6.1" have entirely different histories.
0
.cursor/debug.log
Normal file
0
.cursor/debug.log
Normal file
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,5 +2,4 @@
|
|||||||
.idea
|
.idea
|
||||||
/example/tpt/demo/
|
/example/tpt/demo/
|
||||||
*.exe
|
*.exe
|
||||||
/example/config
|
/example/config
|
||||||
/.cursor/*.log
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "code.hoteas.com/golang/hotime/common"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -249,170 +248,6 @@ func TestHoTimeDBHelperMethods(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestWhereWithORCondition 测试 OR 条件处理是否正确添加括号
|
|
||||||
func TestWhereWithORCondition(t *testing.T) {
|
|
||||||
// 创建 MySQL 数据库实例
|
|
||||||
mysqlDB := &HoTimeDB{
|
|
||||||
Type: "mysql",
|
|
||||||
Prefix: "",
|
|
||||||
}
|
|
||||||
mysqlDB.initDialect()
|
|
||||||
|
|
||||||
// 测试 OR 与普通条件组合 (假设 A: 顺序问题)
|
|
||||||
t.Run("OR with normal condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 1 - OR with normal condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 检查 OR 条件是否被括号包裹
|
|
||||||
if !strings.Contains(where, "(") || !strings.Contains(where, ")") {
|
|
||||||
t.Errorf("OR condition should be wrapped with parentheses, got: %s", where)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有 AND 连接
|
|
||||||
if !strings.Contains(where, "AND") {
|
|
||||||
t.Errorf("OR condition and normal condition should be connected with AND, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试纯 OR 条件(无其他普通条件)
|
|
||||||
t.Run("Pure OR condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 2 - Pure OR condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 检查 OR 条件内部应该用 OR 连接
|
|
||||||
if !strings.Contains(where, "OR") {
|
|
||||||
t.Errorf("OR condition should contain OR keyword, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试多个普通条件与 OR 组合 (假设 A)
|
|
||||||
t.Run("OR with multiple normal conditions", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
"status": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 3 - OR with multiple normal conditions:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
|
|
||||||
// 应该有括号
|
|
||||||
if !strings.Contains(where, "(") {
|
|
||||||
t.Errorf("OR condition should be wrapped with parentheses, got: %s", where)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试嵌套 AND/OR 条件 (假设 B, E)
|
|
||||||
t.Run("Nested AND/OR conditions", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"AND": Map{
|
|
||||||
"phone": "123",
|
|
||||||
"status": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 4 - Nested AND/OR conditions:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试空 OR 条件 (假设 C)
|
|
||||||
t.Run("Empty OR condition", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 5 - Empty OR condition:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试 OR 与 LIMIT, ORDER 组合 (假设 D)
|
|
||||||
t.Run("OR with LIMIT and ORDER", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
"ORDER": "id DESC",
|
|
||||||
"LIMIT": 10,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 6 - OR with LIMIT and ORDER:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试同时有 OR 和 AND 关键字 (假设 E)
|
|
||||||
t.Run("Both OR and AND keywords", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"AND": Map{
|
|
||||||
"type": 1,
|
|
||||||
"source": "web",
|
|
||||||
},
|
|
||||||
"state": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 7 - Both OR and AND keywords:")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 测试普通条件在 OR 之前(排序后)(假设 A)
|
|
||||||
t.Run("Normal condition before OR alphabetically", func(t *testing.T) {
|
|
||||||
data := Map{
|
|
||||||
"OR": Map{
|
|
||||||
"username": "test",
|
|
||||||
"phone": "123",
|
|
||||||
},
|
|
||||||
"active": 1, // 'a' 在 'O' 之前
|
|
||||||
}
|
|
||||||
|
|
||||||
where, params := mysqlDB.where(data)
|
|
||||||
fmt.Println("Test 8 - Normal condition before OR (alphabetically):")
|
|
||||||
fmt.Println(" Generated WHERE:", where)
|
|
||||||
fmt.Println(" Params count:", len(params))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印测试结果(用于调试)
|
// 打印测试结果(用于调试)
|
||||||
func ExampleIdentifierProcessor() {
|
func ExampleIdentifierProcessor() {
|
||||||
// MySQL 示例
|
// MySQL 示例
|
||||||
|
|||||||
28
db/where.go
28
db/where.go
@ -70,8 +70,8 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
|||||||
}
|
}
|
||||||
sort.Strings(testQu)
|
sort.Strings(testQu)
|
||||||
|
|
||||||
// 追踪条件数量,用于自动添加 AND
|
// 追踪普通条件数量,用于自动添加 AND
|
||||||
condCount := 0
|
normalCondCount := 0
|
||||||
|
|
||||||
for _, k := range testQu {
|
for _, k := range testQu {
|
||||||
v := data[k]
|
v := data[k]
|
||||||
@ -79,16 +79,8 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
|||||||
// 检查是否是 AND/OR 条件关键字
|
// 检查是否是 AND/OR 条件关键字
|
||||||
if isConditionKey(k) {
|
if isConditionKey(k) {
|
||||||
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
tw, ts := that.cond(strings.ToUpper(k), v.(Map))
|
||||||
if tw != "" && strings.TrimSpace(tw) != "" {
|
where += tw
|
||||||
// 与前面的条件用 AND 连接
|
res = append(res, ts...)
|
||||||
if condCount > 0 {
|
|
||||||
where += " AND "
|
|
||||||
}
|
|
||||||
// 用括号包裹 OR/AND 组条件
|
|
||||||
where += "(" + strings.TrimSpace(tw) + ")"
|
|
||||||
condCount++
|
|
||||||
res = append(res, ts...)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,11 +95,11 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
|||||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
||||||
if !strings.HasSuffix(k, "[!]") {
|
if !strings.HasSuffix(k, "[!]") {
|
||||||
// IN 空数组 -> 生成永假条件
|
// IN 空数组 -> 生成永假条件
|
||||||
if condCount > 0 {
|
if normalCondCount > 0 {
|
||||||
where += " AND "
|
where += " AND "
|
||||||
}
|
}
|
||||||
where += "1=0 "
|
where += "1=0 "
|
||||||
condCount++
|
normalCondCount++
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -115,11 +107,11 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
|||||||
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
// 检查是否是 NOT IN(带 [!] 后缀)- NOT IN 空数组永真,跳过即可
|
||||||
if !strings.HasSuffix(k, "[!]") {
|
if !strings.HasSuffix(k, "[!]") {
|
||||||
// IN 空数组 -> 生成永假条件
|
// IN 空数组 -> 生成永假条件
|
||||||
if condCount > 0 {
|
if normalCondCount > 0 {
|
||||||
where += " AND "
|
where += " AND "
|
||||||
}
|
}
|
||||||
where += "1=0 "
|
where += "1=0 "
|
||||||
condCount++
|
normalCondCount++
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -127,11 +119,11 @@ func (that *HoTimeDB) where(data Map) (string, []interface{}) {
|
|||||||
tv, vv := that.varCond(k, v)
|
tv, vv := that.varCond(k, v)
|
||||||
if tv != "" {
|
if tv != "" {
|
||||||
// 自动添加 AND 连接符
|
// 自动添加 AND 连接符
|
||||||
if condCount > 0 {
|
if normalCondCount > 0 {
|
||||||
where += " AND "
|
where += " AND "
|
||||||
}
|
}
|
||||||
where += tv
|
where += tv
|
||||||
condCount++
|
normalCondCount++
|
||||||
res = append(res, vv...)
|
res = append(res, vv...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
118
log/logrus.go
118
log/logrus.go
@ -2,13 +2,12 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetLog(path string, showCodeLine bool) *log.Logger {
|
func GetLog(path string, showCodeLine bool) *log.Logger {
|
||||||
@ -74,98 +73,41 @@ func (that *MyHook) Fire(entry *log.Entry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最大框架层数限制 - 超过这个层数后不再跳过,防止误过滤应用层
|
// 对caller进行递归查询, 直到找到非logrus包产生的第一个调用.
|
||||||
const maxFrameworkDepth = 10
|
// 因为filename我获取到了上层目录名, 因此所有logrus包的调用的文件名都是 logrus/...
|
||||||
|
// 因此通过排除logrus开头的文件名, 就可以排除所有logrus包的自己的函数调用
|
||||||
// isHoTimeFrameworkFile 判断是否是 HoTime 框架文件
|
func findCaller(skip int) string {
|
||||||
// 更精确的匹配:只有明确属于框架的文件才会被跳过
|
file := ""
|
||||||
func isHoTimeFrameworkFile(file string) bool {
|
line := 0
|
||||||
// 1. logrus 日志库内部文件
|
for i := 0; i < 10; i++ {
|
||||||
if strings.HasPrefix(file, "logrus/") {
|
file, line = getCaller(skip + i)
|
||||||
return true
|
if !strings.HasPrefix(file, "logrus") {
|
||||||
}
|
j := 0
|
||||||
|
for true {
|
||||||
// 2. Go 运行时文件
|
j++
|
||||||
if strings.HasPrefix(file, "runtime/") {
|
if file == "common/error.go" {
|
||||||
return true
|
file, line = getCaller(skip + i + j)
|
||||||
}
|
}
|
||||||
|
if file == "db/hotimedb.go" {
|
||||||
// 3. HoTime 框架核心文件 - 通过包含 "hotime" 或框架特有文件名来识别
|
file, line = getCaller(skip + i + j)
|
||||||
// 检查路径中是否包含 hotime 框架标识
|
}
|
||||||
lowerFile := strings.ToLower(file)
|
if file == "code/makecode.go" {
|
||||||
if strings.Contains(lowerFile, "hotime") {
|
file, line = getCaller(skip + i + j)
|
||||||
// 是 hotime 框架的一部分,检查是否是核心模块
|
}
|
||||||
frameworkDirs := []string{"/db/", "/common/", "/code/", "/cache/", "/log/", "/dri/"}
|
if strings.Index(file, "common/") == 0 {
|
||||||
for _, dir := range frameworkDirs {
|
file, line = getCaller(skip + i + j)
|
||||||
if strings.Contains(file, dir) {
|
}
|
||||||
return true
|
if strings.Contains(file, "application.go") {
|
||||||
}
|
file, line = getCaller(skip + i + j)
|
||||||
}
|
}
|
||||||
// 框架核心文件(在 hotime 根目录下的 .go 文件)
|
if j == 5 {
|
||||||
if strings.HasSuffix(file, "application.go") ||
|
break
|
||||||
strings.HasSuffix(file, "context.go") ||
|
|
||||||
strings.HasSuffix(file, "session.go") ||
|
|
||||||
strings.HasSuffix(file, "const.go") ||
|
|
||||||
strings.HasSuffix(file, "type.go") ||
|
|
||||||
strings.HasSuffix(file, "var.go") ||
|
|
||||||
strings.HasSuffix(file, "mime.go") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 直接匹配框架核心目录(用于没有完整路径的情况)
|
|
||||||
// 只匹配 "db/xxx.go" 这种在框架核心目录下的文件
|
|
||||||
frameworkCoreDirs := []string{"db/", "common/", "code/", "cache/"}
|
|
||||||
for _, dir := range frameworkCoreDirs {
|
|
||||||
if strings.HasPrefix(file, dir) {
|
|
||||||
// 额外检查:确保不是用户项目中同名目录
|
|
||||||
// 框架文件通常有特定的文件名
|
|
||||||
frameworkFiles := []string{
|
|
||||||
"query.go", "crud.go", "where.go", "builder.go", "db.go",
|
|
||||||
"dialect.go", "aggregate.go", "transaction.go", "identifier.go",
|
|
||||||
"error.go", "func.go", "map.go", "obj.go", "slice.go",
|
|
||||||
"makecode.go", "template.go", "config.go",
|
|
||||||
"cache.go", "cache_db.go", "cache_memory.go", "cache_redis.go",
|
|
||||||
}
|
|
||||||
for _, f := range frameworkFiles {
|
|
||||||
if strings.HasSuffix(file, f) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对caller进行递归查询, 直到找到非框架层产生的第一个调用.
|
|
||||||
// 遍历调用栈,跳过框架层文件,找到应用层代码
|
|
||||||
// 使用层数限制确保不会误过滤应用层同名目录
|
|
||||||
func findCaller(skip int) string {
|
|
||||||
frameworkCount := 0 // 连续框架层计数
|
|
||||||
|
|
||||||
// 遍历调用栈,找到第一个非框架文件
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
file, line := getCaller(skip + i)
|
|
||||||
if file == "" {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if isHoTimeFrameworkFile(file) {
|
|
||||||
frameworkCount++
|
|
||||||
// 层数限制:如果已经跳过太多层,停止跳过
|
|
||||||
if frameworkCount >= maxFrameworkDepth {
|
|
||||||
return fmt.Sprintf("%s:%d", file, line)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找到非框架文件,返回应用层代码位置
|
|
||||||
return fmt.Sprintf("%s:%d", file, line)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果找不到应用层,返回最初的调用者
|
|
||||||
file, line := getCaller(skip)
|
|
||||||
return fmt.Sprintf("%s:%d", file, line)
|
return fmt.Sprintf("%s:%d", file, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user