Browse Source

修改日志输出格式

qdy 2 months ago
parent
commit
de594148d9

+ 61
- 4
config/config.go View File

@@ -96,18 +96,75 @@ func (c *Config) GetInitError() error {
96 96
 	return c.initError
97 97
 }
98 98
 
99
+// GetService 包级便捷函数 - 获取服务配置
100
+func GetService() *subconfigs.ServiceConfig {
101
+	// 直接使用全局的 cfgInstance,假设初始化已完成
102
+	if cfgInstance == nil {
103
+		// 这种情况不应该发生,但为了安全返回默认值
104
+		return &subconfigs.ServiceConfig{}
105
+	}
106
+	return cfgInstance.GetService()
107
+}
108
+
109
+// GetDatabase 包级便捷函数 - 获取数据库配置
110
+func GetDatabase() *subconfigs.DatabaseConfig {
111
+	if cfgInstance == nil {
112
+		return &subconfigs.DatabaseConfig{}
113
+	}
114
+	return cfgInstance.GetDatabase()
115
+}
116
+
117
+// GetRedis 包级便捷函数 - 获取Redis配置
118
+func GetRedis() *subconfigs.RedisConfig {
119
+	if cfgInstance == nil {
120
+		return &subconfigs.RedisConfig{}
121
+	}
122
+	return cfgInstance.GetRedis()
123
+}
124
+
125
+// GetHTTP 包级便捷函数 - 获取HTTP配置
126
+func GetHTTP() *subconfigs.HTTPConfig {
127
+	if cfgInstance == nil {
128
+		return &subconfigs.HTTPConfig{}
129
+	}
130
+	return cfgInstance.GetHTTP()
131
+}
132
+
133
+// GetLog 包级便捷函数 - 获取日志配置
134
+func GetLog() *subconfigs.LogConfig {
135
+	if cfgInstance == nil {
136
+		return &subconfigs.LogConfig{}
137
+	}
138
+	return cfgInstance.GetLog()
139
+}
140
+
141
+func GetMicro() *subconfigs.MicroConfig {
142
+	if cfgInstance == nil {
143
+		return &subconfigs.MicroConfig{}
144
+	}
145
+	return cfgInstance.GetMicro()
146
+
147
+}
148
+
149
+func GetAuth() *subconfigs.AuthConfig {
150
+	if cfgInstance == nil {
151
+		return &subconfigs.AuthConfig{}
152
+	}
153
+	return cfgInstance.GetAuth()
154
+}
155
+
99 156
 var (
100 157
 	cfgInstance *Config
101 158
 	once        sync.Once
102 159
 )
103 160
 
104 161
 // GetConfig 获取主配置(单例模式)
105
-func GetConfig() IConfig {
162
+func GetConfig() (IConfig, error) {
106 163
 	once.Do(func() {
107 164
 		cfgInstance = &Config{}
108 165
 		cfgInstance.initError = LoadConfig() // 加载配置到注册表
109 166
 	})
110
-	return cfgInstance
167
+	return cfgInstance, cfgInstance.initError
111 168
 }
112 169
 
113 170
 // GetInitError 包级函数 - 兼容现有代码调用
@@ -117,8 +174,8 @@ func GetInitError() error {
117 174
 
118 175
 // GetConfigWithError 新方法 - 同时获取配置和错误
119 176
 func GetConfigWithError() (IConfig, error) {
120
-	cfg := GetConfig()
121
-	return cfg, cfg.GetInitError()
177
+	cfg, err := GetConfig()
178
+	return cfg, err
122 179
 }
123 180
 
124 181
 // 实现 IConfig 接口的 IsDatabaseConfigured 方法

+ 2
- 1
config/loader.go View File

@@ -2,6 +2,7 @@ package config
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"log"
5 6
 	"os"
6 7
 	"path/filepath"
7 8
 
@@ -85,7 +86,7 @@ func findConfigFile() (string, error) {
85 86
 			continue
86 87
 		}
87 88
 		if _, err := os.Stat(path); err == nil {
88
-			fmt.Printf("✅ Using config file: %s\n", path)
89
+			log.Printf("✅ Using config file: %s\n", path)
89 90
 			return path, nil
90 91
 		}
91 92
 	}

+ 2
- 3
config/subconfigs/log_config.go View File

@@ -15,7 +15,7 @@ type LogConfig struct {
15 15
 	MaxBackups int    `yaml:"max_backups"`
16 16
 	MaxAge     int    `yaml:"max_age"`
17 17
 	Compress   bool   `yaml:"compress"`
18
-	ESEnabled  bool   `yaml:"es_enabled"`
18
+	//ESEnabled  bool   `yaml:"es_enabled"`
19 19
 	ESPath     string `yaml:"es_path"`
20 20
 	JSONFormat bool   `yaml:"json_format"`
21 21
 }
@@ -28,13 +28,12 @@ func NewLogConfig() *LogConfig {
28 28
 // SetDefaults 设置默认值
29 29
 func (c *LogConfig) SetDefaults() {
30 30
 	c.Level = "info"
31
-	c.Output = "both"
31
+	c.Output = "console"
32 32
 	c.FilePath = "logs/service.log"
33 33
 	c.MaxSize = 100
34 34
 	c.MaxBackups = 3
35 35
 	c.MaxAge = 30
36 36
 	c.Compress = true
37
-	c.ESEnabled = true
38 37
 	c.ESPath = "logs/es-service.log"
39 38
 	c.JSONFormat = true
40 39
 }

+ 41
- 0
ctx/RequestContext.go View File

@@ -0,0 +1,41 @@
1
+package ctx
2
+
3
+import (
4
+	"context"
5
+	"net/http"
6
+)
7
+
8
+// UserContext 用户上下文
9
+type RequestContext struct {
10
+	TraceID      string `json:"trace_id"`
11
+	ServiceName  string `json:"service_name"`
12
+	InstanceName string `json:"instance_name"`
13
+	TenantID     string `json:"tenant_id"`
14
+	UserID       string `json:"user_id"`
15
+}
16
+
17
+// 内部key,不会和其他包冲突
18
+type ctxKey struct{}
19
+
20
+var loggerKey = ctxKey{}
21
+
22
+// Save RequestContext
23
+func SaveContext(r *http.Request, requestContext RequestContext) *http.Request {
24
+	ctx := context.WithValue(r.Context(), loggerKey, requestContext)
25
+	return r.WithContext(ctx)
26
+}
27
+
28
+// Get 从请求获取RequestContext
29
+func GetContext(r *http.Request) *RequestContext {
30
+	if r == nil {
31
+		return &RequestContext{}
32
+	}
33
+
34
+	if v := r.Context().Value(loggerKey); v != nil {
35
+		if loggerCtx, ok := v.(RequestContext); ok {
36
+			return &loggerCtx
37
+		}
38
+	}
39
+
40
+	return &RequestContext{}
41
+}

+ 78
- 0
logger/boot_logger.go View File

@@ -0,0 +1,78 @@
1
+// logger/boot_logger.go
2
+package logger
3
+
4
+import (
5
+	"fmt"
6
+	"io"
7
+	"log"
8
+	"os"
9
+	"path/filepath"
10
+	"time"
11
+)
12
+
13
+var (
14
+	bootLogFile *os.File
15
+	bootLogger  *log.Logger
16
+)
17
+
18
+// InitBootLogger 初始化启动日志(只在启动阶段使用)
19
+func InitBootLog(serviceName string) error {
20
+	// 1. 确保日志目录存在
21
+	logDir := "logs/boot"
22
+	if err := os.MkdirAll(logDir, 0755); err != nil {
23
+		return fmt.Errorf("创建日志目录失败: %v", err)
24
+	}
25
+
26
+	// 2. 创建启动日志文件
27
+	timestamp := time.Now().Format("20060102-150405")
28
+	filename := fmt.Sprintf("boot-%s-%s.log", serviceName, timestamp)
29
+	filePath := filepath.Join(logDir, filename)
30
+
31
+	file, err := os.Create(filePath)
32
+	if err != nil {
33
+		return fmt.Errorf("创建启动日志文件失败: %v", err)
34
+	}
35
+
36
+	bootLogFile = file
37
+
38
+	// 3. 创建专门的bootLogger,同时输出到文件和控制台
39
+	multiWriter := io.MultiWriter(os.Stdout, bootLogFile)
40
+	bootLogger = log.New(multiWriter, "", log.LstdFlags|log.Lshortfile)
41
+
42
+	// 4. 重定向标准库log到bootLogger(重要!)
43
+	log.SetOutput(multiWriter)
44
+	log.SetFlags(log.LstdFlags | log.Lshortfile)
45
+
46
+	// 5. 记录启动信息
47
+	BootLog("=== 服务启动开始 ===")
48
+	BootLog("服务名称: %s", serviceName)
49
+	BootLog("启动时间: %s", time.Now().Format("2006-01-02 15:04:05"))
50
+	BootLog("启动日志文件: %s", filePath)
51
+
52
+	return nil
53
+}
54
+
55
+// BootLog 启动阶段专用日志函数
56
+func BootLog(format string, v ...interface{}) {
57
+	if bootLogger != nil {
58
+		// 使用bootLogger记录
59
+		bootLogger.Printf(format, v...)
60
+	} else {
61
+		// 后备:直接使用标准库log
62
+		log.Printf(format, v...)
63
+	}
64
+}
65
+
66
+// CloseBootLogger 关闭启动日志,切换到运行日志
67
+func CloseBootLogger() {
68
+	if bootLogger != nil {
69
+		BootLog("=== 启动阶段结束,切换到运行时日志 ===")
70
+	}
71
+
72
+	if bootLogFile != nil {
73
+		bootLogFile.Close()
74
+		bootLogFile = nil
75
+	}
76
+
77
+	bootLogger = nil
78
+}

+ 342
- 0
logger/runtime_logger.go View File

@@ -0,0 +1,342 @@
1
+// logger/runtime_logger.go
2
+package logger
3
+
4
+import (
5
+	"encoding/json"
6
+	"log"
7
+	"os"
8
+	"strings"
9
+	"sync"
10
+	"time"
11
+
12
+	"git.x2erp.com/qdy/go-base/config/subconfigs"
13
+	"go.uber.org/zap"
14
+	"go.uber.org/zap/zapcore"
15
+	"gopkg.in/natefinch/lumberjack.v2"
16
+)
17
+
18
+var (
19
+	runtimeLogger  *zap.Logger
20
+	runtimeSugared *zap.SugaredLogger
21
+	serviceName    string
22
+	initOnce       sync.Once
23
+)
24
+
25
+// LogContext 日志上下文
26
+type LogContext struct {
27
+	TraceID      string
28
+	UserID       string
29
+	TenantID     string
30
+	InstanceName string
31
+}
32
+
33
+// ESWriter ES写入器
34
+type ESWriter struct {
35
+	serviceName string
36
+	esURL       string
37
+}
38
+
39
+func NewESWriter(serviceName, esURL string) *ESWriter {
40
+	return &ESWriter{
41
+		serviceName: serviceName,
42
+		esURL:       esURL,
43
+	}
44
+}
45
+
46
+func (w *ESWriter) Write(p []byte) (n int, err error) {
47
+	// 解析日志数据
48
+	var logEntry map[string]interface{}
49
+	if err := json.Unmarshal(p, &logEntry); err != nil {
50
+		// 使用标准log输出到启动日志
51
+		log.Printf("[ES-WRITER] 解析日志JSON失败: %v", err)
52
+		return len(p), nil // 返回成功,不阻塞主流程
53
+	}
54
+
55
+	// 动态生成索引名:service-日期
56
+	indexName := strings.ToLower(w.serviceName) + "-" + time.Now().Format("2006-01-02")
57
+
58
+	// 使用标准log输出到启动日志
59
+	log.Printf("[ES-WRITER] 开始写入ES日志")
60
+	log.Printf("索引名称: %s", indexName)
61
+	log.Printf("数据长度: %d 字节", len(p))
62
+	log.Printf("服务名称: %s", w.serviceName)
63
+	log.Printf("写入时间: %s", time.Now().Format("2006-01-02 15:04:05"))
64
+
65
+	// TODO: 这里是实际写入ES的代码
66
+	// 这里只是示例,实际应该连接ES并写入数据
67
+	// esClient.Index().Index(indexName).BodyJson(logEntry).Do(ctx)
68
+
69
+	// 模拟写入ES(在开发环境可以打印到控制台查看)
70
+	w.simulateESWrite(indexName, logEntry)
71
+
72
+	return len(p), nil
73
+}
74
+
75
+// simulateESWrite 模拟ES写入(仅用于开发调试)
76
+func (w *ESWriter) simulateESWrite(indexName string, data map[string]interface{}) {
77
+	// 在开发环境,可以打印到控制台查看
78
+	if os.Getenv("GO_ENV") == "development" {
79
+		jsonData, _ := json.MarshalIndent(data, "", "  ")
80
+		log.Printf("[ES-SIMULATE] === ES写入模拟开始 ===")
81
+		log.Printf("索引名称: %s", indexName)
82
+		log.Printf("写入时间: %s", time.Now().Format("2006-01-02 15:04:05"))
83
+		log.Printf("jsonData: %s", string(jsonData))
84
+		//log.Printf("[ES-SIMULATE] 索引: %s\n数据:\n%s", indexName, string(jsonData))
85
+	}
86
+}
87
+
88
+func (w *ESWriter) Sync() error {
89
+	// ES客户端通常不需要同步
90
+	log.Printf("[ES-WRITER] 同步ES写入器")
91
+	return nil
92
+}
93
+
94
+// InitRuntimeLogger 初始化
95
+func InitRuntimeLogger(svcName string, logConfig *subconfigs.LogConfig) {
96
+	initOnce.Do(func() {
97
+		if logConfig == nil {
98
+			log.Fatal("错误: 日志配置不能为空")
99
+		}
100
+
101
+		//serviceName = svcName
102
+
103
+		// 创建logger
104
+		if err := createRuntimeLogger(svcName, logConfig); err != nil {
105
+			log.Fatal("创建运行时日志器失败: ", err)
106
+		}
107
+
108
+		log.Printf("[RUNTIME-LOGGER] 运行时日志系统初始化完成")
109
+		log.Printf("服务名称: %s", svcName)
110
+		log.Printf("日志级别: %s", logConfig.Level)
111
+
112
+	})
113
+}
114
+
115
+func createRuntimeLogger(svcName string, cfg *subconfigs.LogConfig) error {
116
+	// 1. 设置日志级别
117
+	level := zap.InfoLevel
118
+	switch strings.ToLower(cfg.Level) {
119
+	case "debug":
120
+		level = zap.DebugLevel
121
+	case "warn":
122
+		level = zap.WarnLevel
123
+	case "error":
124
+		level = zap.ErrorLevel
125
+	}
126
+
127
+	// 2. 编码器配置
128
+	encoderConfig := zap.NewProductionEncoderConfig()
129
+	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
130
+	encoderConfig.TimeKey = "timestamp"
131
+	encoderConfig.MessageKey = "message"
132
+	encoderConfig.LevelKey = "level"
133
+	encoderConfig.CallerKey = "caller"
134
+
135
+	// 3. 根据JSONFormat决定编码器
136
+	var consoleEncoder, jsonEncoder zapcore.Encoder
137
+
138
+	if cfg.JSONFormat {
139
+		// JSON格式输出
140
+		consoleEncoder = zapcore.NewJSONEncoder(encoderConfig)
141
+	} else {
142
+		// 文本格式输出
143
+		consoleEncoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
144
+			TimeKey:        "T",
145
+			LevelKey:       "L",
146
+			NameKey:        "N",
147
+			CallerKey:      "C",
148
+			MessageKey:     "M",
149
+			StacktraceKey:  "S",
150
+			LineEnding:     zapcore.DefaultLineEnding,
151
+			EncodeLevel:    zapcore.CapitalColorLevelEncoder,
152
+			EncodeTime:     zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"),
153
+			EncodeDuration: zapcore.StringDurationEncoder,
154
+			EncodeCaller:   zapcore.ShortCallerEncoder,
155
+		})
156
+	}
157
+
158
+	jsonEncoder = zapcore.NewJSONEncoder(encoderConfig)
159
+
160
+	// 4. 输出目标
161
+	cores := []zapcore.Core{}
162
+
163
+	// 4.1 根据Output字段决定输出目标
164
+	outputs := strings.Split(cfg.Output, ",")
165
+	for _, output := range outputs {
166
+		output = strings.TrimSpace(strings.ToLower(output))
167
+
168
+		switch output {
169
+		case "console", "stdout":
170
+			// 控制台输出
171
+			consoleCore := zapcore.NewCore(
172
+				consoleEncoder,
173
+				zapcore.AddSync(os.Stdout),
174
+				level,
175
+			)
176
+			cores = append(cores, consoleCore)
177
+			log.Printf("[RUNTIME-LOGGER] 初始化控制台日志输出(文本格式)")
178
+
179
+		case "file":
180
+			// 文件输出(JSON格式)
181
+			if cfg.FilePath != "" {
182
+				// 处理文件路径,确保有日期
183
+				filePath := cfg.FilePath
184
+				datedFilePath := addDateToFilePath(filePath)
185
+
186
+				log.Printf("[RUNTIME-LOGGER] 创建运行时日志文件: %s", datedFilePath)
187
+
188
+				fileWriter := zapcore.AddSync(&lumberjack.Logger{
189
+					Filename:   datedFilePath,
190
+					MaxSize:    cfg.MaxSize,    // MB
191
+					MaxBackups: cfg.MaxBackups, // 保留的文件数
192
+					MaxAge:     cfg.MaxAge,     // 天数
193
+					Compress:   cfg.Compress,
194
+				})
195
+
196
+				fileCore := zapcore.NewCore(
197
+					jsonEncoder,
198
+					fileWriter,
199
+					level,
200
+				)
201
+				cores = append(cores, fileCore)
202
+			}
203
+
204
+		case "es":
205
+			// ES输出
206
+
207
+			log.Printf("[RUNTIME-LOGGER] 初始化ES日志输出, 路径: %s", cfg.ESPath)
208
+			esWriter := NewESWriter(svcName, cfg.ESPath)
209
+			esCore := zapcore.NewCore(
210
+				jsonEncoder,
211
+				zapcore.AddSync(esWriter),
212
+				level,
213
+			)
214
+			cores = append(cores, esCore)
215
+
216
+		}
217
+	}
218
+
219
+	// 5. 如果Output为空,默认输出到控制台
220
+	if len(cores) == 0 {
221
+		consoleCore := zapcore.NewCore(
222
+			consoleEncoder,
223
+			zapcore.AddSync(os.Stdout),
224
+			level,
225
+		)
226
+		cores = append(cores, consoleCore)
227
+	}
228
+
229
+	// 6. 创建组合core
230
+	core := zapcore.NewTee(cores...)
231
+
232
+	// 7. 创建logger
233
+	runtimeLogger = zap.New(core,
234
+		zap.AddCaller(),
235
+		zap.AddCallerSkip(1),
236
+		zap.AddStacktrace(zap.ErrorLevel),
237
+		zap.Fields(
238
+			zap.String("service", svcName),
239
+		),
240
+	)
241
+
242
+	runtimeSugared = runtimeLogger.Sugar()
243
+	return nil
244
+}
245
+
246
+// addDateToFilePath 在文件路径中添加日期
247
+func addDateToFilePath(filePath string) string {
248
+	// 如果已经包含 %s,则替换为日期
249
+	if strings.Contains(filePath, "%s") {
250
+		return strings.ReplaceAll(filePath, "%s", time.Now().Format("20060102"))
251
+	}
252
+
253
+	// 如果没有 %s,在扩展名前添加日期
254
+	extIndex := strings.LastIndex(filePath, ".")
255
+	if extIndex > 0 {
256
+		return filePath[:extIndex] + "-" + time.Now().Format("20060102") + filePath[extIndex:]
257
+	}
258
+
259
+	// 没有扩展名,直接在末尾添加日期
260
+	return filePath + "-" + time.Now().Format("20060102")
261
+}
262
+
263
+// WithC 创建带有上下文的logger(C代表Context)
264
+func W(ctx LogContext) *zap.SugaredLogger {
265
+	// 直接使用,不会为空
266
+	return runtimeLogger.With(
267
+		zap.String("service", serviceName),
268
+		zap.String("trace_id", ctx.TraceID),
269
+		zap.String("user_id", ctx.UserID),
270
+		zap.String("tenant_id", ctx.TenantID),
271
+		zap.String("instance_name", ctx.InstanceName),
272
+	).Sugar()
273
+}
274
+
275
+// ============ 上下文日志快捷方法 ============
276
+
277
+// InfoC 带上下文的Info日志
278
+func InfoC(ctx LogContext, format string, args ...interface{}) {
279
+	W(ctx).Infof(format, args...)
280
+}
281
+
282
+// ErrorC 带上下文的Error日志
283
+func ErrorC(ctx LogContext, format string, args ...interface{}) {
284
+	W(ctx).Errorf(format, args...)
285
+}
286
+
287
+// DebugC 带上下文的Debug日志
288
+func DebugC(ctx LogContext, format string, args ...interface{}) {
289
+	W(ctx).Debugf(format, args...)
290
+}
291
+
292
+// WarnC 带上下文的Warn日志
293
+func WarnC(ctx LogContext, format string, args ...interface{}) {
294
+	W(ctx).Warnf(format, args...)
295
+}
296
+
297
+// FatalC 带上下文的Fatal日志
298
+func FatalC(ctx LogContext, format string, args ...interface{}) {
299
+	W(ctx).Fatalf(format, args...)
300
+}
301
+
302
+// ============ 原始日志方法 ============
303
+
304
+// Info 普通Info日志
305
+func Info(format string, args ...interface{}) {
306
+	runtimeSugared.Infof(format, args...)
307
+}
308
+
309
+// Error 普通Error日志
310
+func Error(format string, args ...interface{}) {
311
+	runtimeSugared.Errorf(format, args...)
312
+}
313
+
314
+// Debug 普通Debug日志
315
+func Debug(format string, args ...interface{}) {
316
+	runtimeSugared.Debugf(format, args...)
317
+}
318
+
319
+// Warn 普通Warn日志
320
+func Warn(format string, args ...interface{}) {
321
+	runtimeSugared.Warnf(format, args...)
322
+}
323
+
324
+// Fatal 普通Fatal日志
325
+func Fatal(format string, args ...interface{}) {
326
+	runtimeSugared.Fatalf(format, args...)
327
+}
328
+
329
+// ============ 其他方法 ============
330
+
331
+// GetZapLogger 获取原始zap.Logger
332
+func GetZapLogger() *zap.Logger {
333
+	return runtimeLogger
334
+}
335
+
336
+// Sync 同步日志
337
+func Sync() error {
338
+	if runtimeLogger != nil {
339
+		return runtimeLogger.Sync()
340
+	}
341
+	return nil
342
+}

+ 12
- 0
middleware/authMiddleware.go View File

@@ -6,6 +6,7 @@ import (
6 6
 	"strings"
7 7
 	"time"
8 8
 
9
+	"git.x2erp.com/qdy/go-base/ctx"
9 10
 	"git.x2erp.com/qdy/go-base/types"
10 11
 )
11 12
 
@@ -43,6 +44,17 @@ func JWTAuthMiddleware(next http.Handler) http.Handler {
43 44
 			return
44 45
 		}
45 46
 
47
+		//保存上下文
48
+		// 创建LoggerContext(从token解析用户信息)
49
+		requestContext := ctx.RequestContext{
50
+			TraceID:  "trace_id-123", // 生成追踪ID
51
+			TenantID: "tenant-123",   // 从token解析
52
+			UserID:   "user-456",     // 从token解析
53
+		}
54
+
55
+		// 保存到请求
56
+		r = ctx.SaveContext(r, requestContext)
57
+
46 58
 		// 继续处理请求
47 59
 		next.ServeHTTP(w, r)
48 60
 	})

+ 2
- 2
test.go View File

@@ -8,10 +8,10 @@ import (
8 8
 
9 9
 func main() {
10 10
 	// 获取配置实例
11
-	cfg := config.GetConfig()
11
+	cfg, err := config.GetConfig()
12 12
 
13 13
 	// 检查初始化是否有错误
14
-	if err := config.GetInitError(); err != nil {
14
+	if err != nil {
15 15
 		fmt.Printf("配置加载失败: %v\n", err)
16 16
 		return
17 17
 	}

+ 12
- 9
types/types.go View File

@@ -2,6 +2,8 @@ package types
2 2
 
3 3
 import (
4 4
 	"time"
5
+
6
+	"git.x2erp.com/qdy/go-base/ctx"
5 7
 )
6 8
 
7 9
 // ExchangeRequest 创建交换机的请求
@@ -104,15 +106,16 @@ type QueryRequest struct {
104 106
 
105 107
 // QueryResult 查询结果
106 108
 type QueryResult struct {
107
-	Success    bool          `json:"success"`
108
-	Data       interface{}   `json:"data,omitempty"`
109
-	Error      string        `json:"error,omitempty"`
110
-	Count      int           `json:"count,omitempty"`
111
-	Time       string        `json:"time,omitempty"`
112
-	QueryTime  time.Duration `json:"queryTime,omitempty"`
113
-	SaveTime   time.Duration `json:"saveTime,omitempty"`
114
-	TotalCount int           `json:"totalCount,omitempty"`
115
-	Message    string        `json:"message,omitempty"`
109
+	Success    bool                `json:"success"`
110
+	Data       interface{}         `json:"data,omitempty"`
111
+	Error      string              `json:"error,omitempty"`
112
+	Count      int                 `json:"count,omitempty"`
113
+	Time       string              `json:"time,omitempty"`
114
+	QueryTime  time.Duration       `json:"queryTime,omitempty"`
115
+	SaveTime   time.Duration       `json:"saveTime,omitempty"`
116
+	TotalCount int                 `json:"totalCount,omitempty"`
117
+	Message    string              `json:"message,omitempty"`
118
+	Metadata   *ctx.RequestContext `json:"metadata,omitempty"`
116 119
 }
117 120
 
118 121
 // PageResult 分页结果

Loading…
Cancel
Save