修复: 粘贴图失效与SFTP串台回退等一批问题
粘贴图根修(Ctrl+V直读系统剪贴板,不再依赖不可靠的paste事件)/SFTP串台双刀(拨号期禁回退本机+盘符路径自动回根)/PDF导出目录设置与文案带落盘路径/性能批(连接保存下载防重/md渲染防抖+预热/hljs按需加载/审计锁外置/快捷方式解析超时/webview-watch可停)/前端62类型错清零(含keyPath蛇形字段炸弹)/目录图标字段错位修复
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetLogsDir 日志目录:~/.u-desk/logs(audit 日志同目录)
|
||||
func GetLogsDir() string {
|
||||
return filepath.Join(GetUserDataDir(), "logs")
|
||||
}
|
||||
|
||||
// dailyFile 按天滚动的追加写文件:每次写时发现日期变化即切换新文件。
|
||||
// 只做按天滚动,不搞大小上限/压缩/清理,崩溃取证够用。
|
||||
type dailyFile struct {
|
||||
mu sync.Mutex
|
||||
dir string
|
||||
naming func(t time.Time) string
|
||||
f *os.File
|
||||
day string
|
||||
}
|
||||
|
||||
func newDailyFile(dir string, naming func(time.Time) string) *dailyFile {
|
||||
return &dailyFile{dir: dir, naming: naming}
|
||||
}
|
||||
|
||||
func (w *dailyFile) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
day := now.Format("2006-01-02")
|
||||
if w.f != nil && day != w.day {
|
||||
w.f.Close()
|
||||
w.f = nil
|
||||
}
|
||||
if w.f == nil {
|
||||
if err := os.MkdirAll(w.dir, 0755); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f, err := os.OpenFile(filepath.Join(w.dir, w.naming(now)), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.f, w.day = f, day
|
||||
}
|
||||
return w.f.Write(p)
|
||||
}
|
||||
|
||||
func (w *dailyFile) Close() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if w.f == nil {
|
||||
return nil
|
||||
}
|
||||
err := w.f.Close()
|
||||
w.f = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// FileLogOut 应用文件日志的底层写出器(stderr + 按天文件 MultiWriter)。
|
||||
// SetupFileLogging 之后有效;供需要独立过滤级别的 logger 复用同一落盘目的地
|
||||
// (如 wails 系统日志降到 Warn,业务 slog.Default() 保持 INFO 全量)。
|
||||
var FileLogOut io.Writer = os.Stderr
|
||||
|
||||
// SetupFileLogging slog 双路落盘:stderr + ~/.u-desk/logs/app-YYYY-MM-DD.log。
|
||||
// windowsgui 下 stderr 被丢弃,文件路是日志唯一可见载体;
|
||||
// 文件侧打不开时静默降级为仅 stderr,不因日志失败阻断启动。
|
||||
func SetupFileLogging() {
|
||||
out := io.Writer(os.Stderr)
|
||||
w := newDailyFile(GetLogsDir(), func(t time.Time) string {
|
||||
return fmt.Sprintf("app-%s.log", t.Format("2006-01-02"))
|
||||
})
|
||||
// 预开当天文件探测文件侧可用性(Write 内部含懒开/换天逻辑)
|
||||
if _, err := w.Write(nil); err == nil {
|
||||
// 文件必须放 MultiWriter 首位:windowsgui 下 stderr 句柄无效,
|
||||
// MultiWriter 按序短路,stderr 在前会吞掉后续文件路(真机零日志的根因)
|
||||
out = io.MultiWriter(w, os.Stderr)
|
||||
} else {
|
||||
w.Close()
|
||||
}
|
||||
FileLogOut = out
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(out, nil)))
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var panicLogMu sync.Mutex
|
||||
|
||||
// AppendPanicLog 把一次 panic 的来源名、值与完整栈追加写入
|
||||
// ~/.u-desk/logs/panic-YYYYMMDD.log。写失败静默放弃:取证失败不能引发二次故障。
|
||||
func AppendPanicLog(name string, r any) {
|
||||
dir := GetLogsDir()
|
||||
panicLogMu.Lock()
|
||||
defer panicLogMu.Unlock()
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
path := filepath.Join(dir, fmt.Sprintf("panic-%s.log", time.Now().Format("20060102")))
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
// debug.Stack 在 defer 内 panic 未展开时调用,包含 panic 原始调用栈
|
||||
fmt.Fprintf(f, "[%s] goroutine=%s panic=%v\n%s\n",
|
||||
time.Now().Format("2006-01-02 15:04:05.000"), name, r, debug.Stack())
|
||||
}
|
||||
|
||||
// Go 带 panic 兜底的 goroutine:panic 时写完整取证后仅结束该 goroutine,
|
||||
// 不让单点 panic 杀掉整个进程(windowsgui 下默认静默退出零痕迹)。
|
||||
func Go(name string, fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AppendPanicLog(name, r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
@@ -199,10 +199,11 @@ func RunConcurrent[T any](tasks []T, limit int, fn func(t T)) {
|
||||
for _, task := range tasks {
|
||||
sem <- struct{}{}
|
||||
wg.Add(1)
|
||||
go func(t T) {
|
||||
t := task
|
||||
Go("bounded-parallel", func() {
|
||||
defer func() { <-sem; wg.Done() }()
|
||||
fn(t)
|
||||
}(task)
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user