diff --git a/code/base-project/base-app/test2.html b/code/base-project/base-app/test2.html index 749b1d68..f47df07b 100644 --- a/code/base-project/base-app/test2.html +++ b/code/base-project/base-app/test2.html @@ -2,78 +2,195 @@ -DataGrid 单行编辑 + wdatepicker 示例 - - - +DataGrid 多选删除 + 单行编辑示例 + - + + + + -

DataGrid 单行编辑 + wdatepicker

+

DataGrid 编辑 + 多选删除 + 彻底删除 示例

- + - - - + + +
IDName出生日期Score姓名出生日期成绩
- 保存当前行 + 保存当前行 + 删除所选 + 彻底删除
+ +// 生成随机数据 +function createMockData(count = 100){ + let data = []; + for(let i = 1; i <= count; i++){ + data.push({ + id: i, + name: "用户" + i, + birth: randomDate("1980-01-01", "2005-12-31"), + score: Math.floor(Math.random() * 41) + 60 + }); + } + return data; +} + +// 生成随机日期 +function randomDate(start, end){ + let startTime = new Date(start).getTime(); + let endTime = new Date(end).getTime(); + let randomTime = startTime + Math.random() * (endTime - startTime); + let d = new Date(randomTime); + + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; +} + + +// 批量删除(不立即提交) +function removeRows() { + let rows = $('#dg').datagrid('getChecked'); + if(rows.length === 0){ + $.messager.alert('提示', '请先勾选要删除的行'); + return; + } + + // 倒序删除避免索引错乱 + let indexes = rows.map(row => $('#dg').datagrid('getRowIndex', row)) + .sort((a,b) => b - a); + + indexes.forEach(index => $('#dg').datagrid('deleteRow', index)); + + + $('#dg').datagrid('clearSelections'); + $('#dg').datagrid('clearChecked'); +} + + +// 获取真正删除的数据 +function confirmDelete(){ + let deleted = $('#dg').datagrid('getChanges','deleted'); + console.log("真正需要提交到后台删除的数据:", deleted); + let updated = $('#dg').datagrid('getChanges','updated'); + console.log("updated:", updated); + let inserted = $('#dg').datagrid('getChanges','inserted'); + console.log("inserted:", inserted); +} + +let currentCell = { rowIndex: null, field: null }; + +// 监听编辑器加载,记录焦点单元格 +$('#dg').datagrid({ + onBeginEdit: function (rowIndex, rowData) { + + let editors = $(this).datagrid('getEditors', rowIndex); + + editors.forEach(e => { + + $(e.target).off('focus').on('focus', function () { + currentCell.rowIndex = rowIndex; + currentCell.field = e.field; + + // 获取所有列 field + let fields = $('#dg').datagrid('getColumnFields', true) // true = 冻结列 + .concat($('#dg').datagrid('getColumnFields', false)); // false = 普通列 + + let colIndex = fields.indexOf(e.field); + + console.log(`👉 聚焦位置:行=${currentCell.rowIndex}, 列索引=${colIndex}, field=${currentCell.field}`); + }); + + + // 监听粘贴事件 + $(e.target).off('paste').on('paste', function (event) { + setTimeout(() => { // 延迟确保粘贴内容已写入 input + let pastedValue = $(this).val(); + console.log(`📌 粘贴事件触发:行=${currentCell.rowIndex}, 列=${currentCell.field}, 内容=${pastedValue}`); + + handlePaste(currentCell.rowIndex, currentCell.field, pastedValue); + }, 10); + }); + }); + } +}); + + diff --git a/code/base-project/base-go-v2/internal/api/auth/index.go b/code/base-project/base-go-v2/internal/api/auth/index.go index 7d6be2cf..a29ec86f 100644 --- a/code/base-project/base-go-v2/internal/api/auth/index.go +++ b/code/base-project/base-go-v2/internal/api/auth/index.go @@ -10,6 +10,8 @@ import ( "base-go-v2/internal/utils/jwtx" "base-go-v2/internal/utils/mapx" "base-go-v2/internal/utils/timex" + "fmt" + "time" ) func InitAuthRouter(r *router.Router) { @@ -69,10 +71,14 @@ func loginDefault(c *router.Context) error { "account", account, "password", password, ) + start := time.Now() + user, err := db.SelectOne("user_info", db.BuildWhereString(userQuery), "") if err != nil { return response.Success(c).Message("查询用户失败,请稍后重试").Send() } + duration := time.Since(start) // 耗时 + fmt.Printf("登录查询: (耗时: %v)\n", duration) if user == nil { // 账号或密码错误 diff --git a/code/base-project/base-go-v3/biz/handler/auth/login_service.go b/code/base-project/base-go-v3/biz/handler/auth/login_service.go index 1579c74d..7d04de9b 100644 --- a/code/base-project/base-go-v3/biz/handler/auth/login_service.go +++ b/code/base-project/base-go-v3/biz/handler/auth/login_service.go @@ -8,6 +8,8 @@ import ( "cn/oneao/base-go/utils/jwtx" "cn/oneao/base-go/utils/response" "context" + "fmt" + "time" auth "cn/oneao/base-go/biz/model/auth" @@ -25,13 +27,18 @@ func LoginDefault(ctx context.Context, c *app.RequestContext) { c.String(consts.StatusBadRequest, err.Error()) return } - // 处理登录 + // 假设 ctx 已有,req.Account / req.Password 已有 + start := time.Now() + loginUser, err := db.Client.UserInfo. Query(). Where(userinfo.AccountEQ(req.Account), userinfo.PasswordEQ(req.Password)). Only(ctx) + duration := time.Since(start) // 耗时 + fmt.Printf("登录查询: (耗时: %v)\n", duration) + if err != nil { c.String(consts.StatusInternalServerError, "账号或密码错误") return diff --git a/code/base-project/base-go-v3/biz/router/middleware/error.go b/code/base-project/base-go-v3/biz/router/middleware/error.go index 6b05f9cd..e4fe505f 100644 --- a/code/base-project/base-go-v3/biz/router/middleware/error.go +++ b/code/base-project/base-go-v3/biz/router/middleware/error.go @@ -13,7 +13,7 @@ func Error() app.HandlerFunc { if err := c.Errors.Last(); err != nil { // 未知错误 -> 500 - c.JSON(500, ErrorResponse{500, "Internal Server Error"}) + //c.JSON(500, ErrorResponse{500, "Internal Server Error"}) } } } diff --git a/code/base-project/base-go-v3/biz/router/middleware/log.go b/code/base-project/base-go-v3/biz/router/middleware/log.go new file mode 100644 index 00000000..4621b2cc --- /dev/null +++ b/code/base-project/base-go-v3/biz/router/middleware/log.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "context" + "time" + + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/hlog" + "github.com/hertz-contrib/requestid" + "go.uber.org/zap" +) + +func Log() app.HandlerFunc { + return func(c context.Context, ctx *app.RequestContext) { + start := time.Now() // 记录开始时间 + + // 执行后续处理 + ctx.Next(c) + + // 计算耗时 + duration := time.Since(start).Milliseconds() + + hlog.Info("请求完成", + zap.String("RequestID", requestid.Get(ctx)), + zap.String("IP", ctx.ClientIP()), + zap.String("Method", string(ctx.Method())), + zap.String("Path", string(ctx.Path())), + zap.Int("Status", ctx.GetResponse().StatusCode()), + zap.Int64("Duration", duration), + ) + } +} diff --git a/code/base-project/base-go-v3/log/log.go b/code/base-project/base-go-v3/log/log.go index b9c70029..cb5b5b73 100644 --- a/code/base-project/base-go-v3/log/log.go +++ b/code/base-project/base-go-v3/log/log.go @@ -1,49 +1,105 @@ package log import ( - "log" + "fmt" "os" - "path" + "sync" "time" "github.com/cloudwego/hertz/pkg/common/hlog" - hertzzap "github.com/hertz-contrib/logger/zap" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) func InitLog() { - // Customizable output directory. - var logFilePath string - dir := "./hlog" - logFilePath = dir + "/logs/" - if err := os.MkdirAll(logFilePath, 0o777); err != nil { - log.Println(err.Error()) - return - } + dynamicLevel := zap.NewAtomicLevel() - // Set filename to date - logFileName := time.Now().Format("2006-01-02") + ".log" - fileName := path.Join(logFilePath, logFileName) - if _, err := os.Stat(fileName); err != nil { - if _, err := os.Create(fileName); err != nil { - log.Println(err.Error()) - return - } - } + dynamicLevel.SetLevel(zap.DebugLevel) - logger := hertzzap.NewLogger(hertzzap.WithCores([]hertzzap.CoreConfig{})) - - lumberjackLogger := &lumberjack.Logger{ - Filename: fileName, - MaxSize: 20, // A file can be up to 20M. - MaxBackups: 5, // Save up to 5 files at the same time. - MaxAge: 10, // A file can exist for a maximum of 10 days. - Compress: true, // Compress with gzip. - } - - logger.SetOutput(lumberjackLogger) - logger.SetLevel(hlog.LevelDebug) + logger := NewLogger( + WithCores([]CoreConfig{ + { + Enc: zapcore.NewJSONEncoder(humanEncoderConfig()), + Ws: zapcore.AddSync(os.Stdout), + Lvl: dynamicLevel, + }, + { + Enc: zapcore.NewJSONEncoder(humanEncoderConfig()), + Ws: getWriteSyncer("./logs/debug"), + Lvl: zap.LevelEnablerFunc(func(lev zapcore.Level) bool { + return lev == zap.DebugLevel + }), + }, + { + Enc: zapcore.NewJSONEncoder(humanEncoderConfig()), + Ws: getWriteSyncer("./logs/info"), + Lvl: zap.LevelEnablerFunc(func(lev zapcore.Level) bool { + return lev == zap.InfoLevel + }), + }, + { + Enc: zapcore.NewJSONEncoder(humanEncoderConfig()), + Ws: getWriteSyncer("./logs/warn"), + Lvl: zap.LevelEnablerFunc(func(lev zapcore.Level) bool { + return lev == zap.WarnLevel + }), + }, + { + Enc: zapcore.NewJSONEncoder(humanEncoderConfig()), + Ws: getWriteSyncer("./logs/error"), + Lvl: zap.LevelEnablerFunc(func(lev zapcore.Level) bool { + return lev >= zap.ErrorLevel + }), + }, + }...), + ) + defer logger.Sync() hlog.SetLogger(logger) } + +// humanEncoderConfig copy from zap +func humanEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + MessageKey: "Message", + LevelKey: "Level", + NameKey: "Name", + TimeKey: "Ts", + CallerKey: "Caller", + FunctionKey: "Func", + StacktraceKey: "Stacktrace", + LineEnding: "\n", + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +var ( + mu sync.Mutex + currentDay = make(map[string]string) + currentSync = make(map[string]zapcore.WriteSyncer) +) + +func getWriteSyncer(basePath string) zapcore.WriteSyncer { + mu.Lock() + defer mu.Unlock() + + today := time.Now().Format("2006-01-02") + if day, ok := currentDay[basePath]; !ok || day != today { + currentDay[basePath] = today + file := fmt.Sprintf("%s/%s.log", basePath, today) + + currentSync[basePath] = zapcore.AddSync(&lumberjack.Logger{ + Filename: file, + MaxSize: 10, + MaxBackups: 50000, + MaxAge: 5, + Compress: true, + LocalTime: true, + }) + } + return currentSync[basePath] +} diff --git a/code/base-project/base-go-v3/log/logger.go b/code/base-project/base-go-v3/log/logger.go new file mode 100644 index 00000000..29e14215 --- /dev/null +++ b/code/base-project/base-go-v3/log/logger.go @@ -0,0 +1,268 @@ +package log + +import ( + "context" + "fmt" + "io" + + "github.com/cloudwego/hertz/pkg/common/hlog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var _ hlog.FullLogger = (*Logger)(nil) + +type Logger struct { + l *zap.Logger + config *config +} + +func NewLogger(opts ...Option) *Logger { + config := defaultConfig() + + // apply options + for _, opt := range opts { + opt.apply(config) + } + + cores := make([]zapcore.Core, 0, len(config.coreConfigs)) + for _, coreConfig := range config.coreConfigs { + cores = append(cores, zapcore.NewCore(coreConfig.Enc, coreConfig.Ws, coreConfig.Lvl)) + } + + logger := zap.New( + zapcore.NewTee(cores[:]...), + config.zapOpts...) + + return &Logger{ + l: logger, + config: config, + } +} + +// GetExtraKeys get extraKeys from logger config +func (l *Logger) GetExtraKeys() []ExtraKey { + return l.config.extraKeys +} + +// PutExtraKeys add extraKeys after init +func (l *Logger) PutExtraKeys(keys ...ExtraKey) { + for _, k := range keys { + if !InArray(k, l.config.extraKeys) { + l.config.extraKeys = append(l.config.extraKeys, k) + } + } +} + +func (l *Logger) Log(level hlog.Level, kvs ...interface{}) { + if len(kvs) == 0 { + return + } + + // 第一个参数作为 msg + msg := fmt.Sprint(kvs[0]) + + // 后续参数作为 zap.Field + fields := make([]zap.Field, 0, len(kvs)-1) + for _, v := range kvs[1:] { + if f, ok := v.(zap.Field); ok { + fields = append(fields, f) + } + } + + switch level { + case hlog.LevelTrace, hlog.LevelDebug: + l.l.Debug(msg, fields...) + case hlog.LevelInfo: + l.l.Info(msg, fields...) + case hlog.LevelNotice, hlog.LevelWarn: + l.l.Warn(msg, fields...) + case hlog.LevelError: + l.l.Error(msg, fields...) + case hlog.LevelFatal: + l.l.Fatal(msg, fields...) + default: + l.l.Warn(msg, fields...) + } +} + +func (l *Logger) Logf(level hlog.Level, format string, kvs ...interface{}) { + logger := l.l.Sugar().With() + switch level { + case hlog.LevelTrace, hlog.LevelDebug: + logger.Debugf(format, kvs...) + case hlog.LevelInfo: + logger.Infof(format, kvs...) + case hlog.LevelNotice, hlog.LevelWarn: + logger.Warnf(format, kvs...) + case hlog.LevelError: + logger.Errorf(format, kvs...) + case hlog.LevelFatal: + logger.Fatalf(format, kvs...) + default: + logger.Warnf(format, kvs...) + } +} + +func (l *Logger) CtxLogf(level hlog.Level, ctx context.Context, format string, kvs ...interface{}) { + zLevel := hLevelToZapLevel(level) + if !l.config.coreConfigs[0].Lvl.Enabled(zLevel) { + return + } + zapLogger := l.l + if len(l.config.extraKeys) > 0 { + for _, k := range l.config.extraKeys { + if l.config.extraKeyAsStr { + v := ctx.Value(string(k)) + if v != nil { + zapLogger = zapLogger.With(zap.Any(string(k), v)) + } + } else { + v := ctx.Value(k) + if v != nil { + zapLogger = zapLogger.With(zap.Any(string(k), v)) + } + } + } + } + log := zapLogger.Sugar() + switch level { + case hlog.LevelDebug, hlog.LevelTrace: + log.Debugf(format, kvs...) + case hlog.LevelInfo: + log.Infof(format, kvs...) + case hlog.LevelNotice, hlog.LevelWarn: + log.Warnf(format, kvs...) + case hlog.LevelError: + log.Errorf(format, kvs...) + case hlog.LevelFatal: + log.Fatalf(format, kvs...) + default: + log.Warnf(format, kvs...) + } +} + +func (l *Logger) Trace(v ...interface{}) { + l.Log(hlog.LevelTrace, v...) +} + +func (l *Logger) Debug(v ...interface{}) { + l.Log(hlog.LevelDebug, v...) +} + +func (l *Logger) Info(v ...interface{}) { + l.Log(hlog.LevelInfo, v...) +} + +func (l *Logger) Notice(v ...interface{}) { + l.Log(hlog.LevelNotice, v...) +} + +func (l *Logger) Warn(v ...interface{}) { + l.Log(hlog.LevelWarn, v...) +} + +func (l *Logger) Error(v ...interface{}) { + l.Log(hlog.LevelError, v...) +} + +func (l *Logger) Fatal(v ...interface{}) { + l.Log(hlog.LevelFatal, v...) +} + +func (l *Logger) Tracef(format string, v ...interface{}) { + l.Logf(hlog.LevelTrace, format, v...) +} + +func (l *Logger) Debugf(format string, v ...interface{}) { + l.Logf(hlog.LevelDebug, format, v...) +} + +func (l *Logger) Infof(format string, v ...interface{}) { + l.Logf(hlog.LevelInfo, format, v...) +} + +func (l *Logger) Noticef(format string, v ...interface{}) { + l.Logf(hlog.LevelWarn, format, v...) +} + +func (l *Logger) Warnf(format string, v ...interface{}) { + l.Logf(hlog.LevelWarn, format, v...) +} + +func (l *Logger) Errorf(format string, v ...interface{}) { + l.Logf(hlog.LevelError, format, v...) +} + +func (l *Logger) Fatalf(format string, v ...interface{}) { + l.Logf(hlog.LevelFatal, format, v...) +} + +func (l *Logger) CtxTracef(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelDebug, ctx, format, v...) +} + +func (l *Logger) CtxDebugf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelDebug, ctx, format, v...) +} + +func (l *Logger) CtxInfof(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelInfo, ctx, format, v...) +} + +func (l *Logger) CtxNoticef(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelWarn, ctx, format, v...) +} + +func (l *Logger) CtxWarnf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelWarn, ctx, format, v...) +} + +func (l *Logger) CtxErrorf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelError, ctx, format, v...) +} + +func (l *Logger) CtxFatalf(ctx context.Context, format string, v ...interface{}) { + l.CtxLogf(hlog.LevelFatal, ctx, format, v...) +} + +func (l *Logger) SetLevel(level hlog.Level) { + lvl := hLevelToZapLevel(level) + + l.config.coreConfigs[0].Lvl = lvl + + cores := make([]zapcore.Core, 0, len(l.config.coreConfigs)) + for _, coreConfig := range l.config.coreConfigs { + cores = append(cores, zapcore.NewCore(coreConfig.Enc, coreConfig.Ws, coreConfig.Lvl)) + } + + logger := zap.New( + zapcore.NewTee(cores[:]...), + l.config.zapOpts...) + + l.l = logger +} + +func (l *Logger) SetOutput(writer io.Writer) { + l.config.coreConfigs[0].Ws = zapcore.AddSync(writer) + + cores := make([]zapcore.Core, 0, len(l.config.coreConfigs)) + for _, coreConfig := range l.config.coreConfigs { + cores = append(cores, zapcore.NewCore(coreConfig.Enc, coreConfig.Ws, coreConfig.Lvl)) + } + + logger := zap.New( + zapcore.NewTee(cores[:]...), + l.config.zapOpts...) + + l.l = logger +} + +// Logger is used to return an instance of *zap.Logger for custom fields, etc. +func (l *Logger) Logger() *zap.Logger { + return l.l +} + +func (l *Logger) Sync() { + _ = l.l.Sync() +} diff --git a/code/base-project/base-go-v3/log/utils.go b/code/base-project/base-go-v3/log/utils.go index 3faf01ca..2c439e81 100644 --- a/code/base-project/base-go-v3/log/utils.go +++ b/code/base-project/base-go-v3/log/utils.go @@ -1,17 +1,3 @@ -// Copyright 2022 CloudWeGo Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package log import ( diff --git a/code/base-project/base-go-v3/main.go b/code/base-project/base-go-v3/main.go index 79fcafd4..61235d72 100644 --- a/code/base-project/base-go-v3/main.go +++ b/code/base-project/base-go-v3/main.go @@ -1,42 +1,34 @@ package main import ( + "cn/oneao/base-go/biz/router/middleware" "cn/oneao/base-go/config" "cn/oneao/base-go/db" "cn/oneao/base-go/log" "cn/oneao/base-go/utils/idgen" + "github.com/cloudwego/hertz/pkg/app/server" - "github.com/cloudwego/hertz/pkg/common/hlog" "github.com/hertz-contrib/requestid" ) func main() { - // 初始化雪花id options := idgen.NewIdGeneratorOptions(config.App.Snowflake.WorkerID) idgen.SetIdGenerator(options) // 初始化数据库 db.InitDB() + // 日志 + log.InitLog() h := server.Default( server.WithBasePath(config.App.Server.BaseUrl), server.WithHostPorts(config.App.Server.Port), ) - // 请求 id + // 全局中间件 h.Use(requestid.New()) - - log.InitLog() - - type User struct { - Name string - Age int - } - - u := User{"tom", 20} - - hlog.Info(u) + h.Use(middleware.Log()) register(h) h.Spin()