Private
Public Access
1
0

新增:Markdown编辑器/数据库优化/安全修复

- Markdown 编辑器:实时预览、PDF 导出、独立查看器
- 数据库优化:动态连接池、查询缓存、Redis Pipeline
- 窗口置顶功能
- 文件系统增强:右键菜单、编辑器集成、收藏夹重构
- 安全修复:XSS 防护、路径穿越、HTML 注入
- 代码质量:正则预编译、缓存锁优化、死代码清理
This commit is contained in:
2026-03-31 09:18:06 +08:00
parent 5f94ccf13b
commit e5dbe89a6f
59 changed files with 5289 additions and 1316 deletions

159
app.go
View File

@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/jung-kurt/gofpdf"
"u-desk/internal/api"
"u-desk/internal/common"
"u-desk/internal/database"
@@ -23,15 +24,17 @@ import (
// App 应用结构体
type App struct {
ctx context.Context
db *database.DB
connectionAPI *api.ConnectionAPI
sqlAPI *api.SqlAPI
tabAPI *api.TabAPI
updateAPI *api.UpdateAPI
configAPI *api.ConfigAPI
fileServer *http.Server
filesystem *filesystem.FileSystemService
ctx context.Context
db *database.DB
connectionAPI *api.ConnectionAPI
sqlAPI *api.SqlAPI
tabAPI *api.TabAPI
updateAPI *api.UpdateAPI
configAPI *api.ConfigAPI
pdfAPI *api.PdfAPI
fileServer *http.Server
filesystem *filesystem.FileSystemService
isAlwaysOnTop bool
}
// NewApp 创建新的应用实例
@@ -60,6 +63,17 @@ func (a *App) Startup(ctx context.Context) {
// 2.5. 迁移旧配置
_ = a.configAPI.MigrateTabConfig()
// 2.6. 初始化PDF导出API
fmt.Println("[启动] 初始化PDF导出模块...")
pdfAPI, err := api.NewPdfAPI()
if err != nil {
fmt.Printf("[启动] PDF导出API初始化失败: %v\n", err)
// PDF导出失败不应影响应用启动所以只警告不panic
} else {
a.pdfAPI = pdfAPI
fmt.Println("[启动] PDF导出模块初始化完成")
}
// 3. 初始化版本号(提前触发缓存,避免后续重复计算)
version := service.GetCurrentVersion()
fmt.Printf("[启动] 当前版本: %s\n", version)
@@ -545,6 +559,16 @@ func (a *App) WindowIsMaximized() bool {
return false
}
// WindowToggleAlwaysOnTop 切换窗口置顶
func (a *App) WindowToggleAlwaysOnTop() bool {
if a.ctx == nil {
return false
}
a.isAlwaysOnTop = !a.isAlwaysOnTop
runtime.WindowSetAlwaysOnTop(a.ctx, a.isAlwaysOnTop)
return a.isAlwaysOnTop
}
// ========== SQL 标签页管理接口 ==========
// SaveSqlTabs 保存 SQL 标签页列表
@@ -630,7 +654,11 @@ func (a *App) startAutoUpdateCheck() {
}
config, err := a.updateAPI.GetUpdateConfig()
if err != nil || !config["success"].(bool) {
if err != nil {
return
}
success, ok := config["success"].(bool)
if !ok || !success {
return
}
@@ -863,3 +891,114 @@ func (a *App) initFilesystemModule() {
fmt.Println("[模块] 文件系统模块初始化完成")
}
// ExportPDF 导出PDF文件
func (a *App) ExportPDF(content string, title string, fileName string, fontSize int, pageWidth int, pageHeight int) (map[string]interface{}, error) {
if a.pdfAPI == nil {
return map[string]interface{}{
"success": false,
"message": "PDF导出功能未初始化",
}, fmt.Errorf("PDF导出功能未初始化")
}
req := api.PdfExportRequest{
Content: content,
Title: title,
FileName: fileName,
FontSize: fontSize,
PageWidth: pageWidth,
PageHeight: pageHeight,
}
result, err := a.pdfAPI.ExportMarkdownToPDF(req)
if err != nil {
return map[string]interface{}{
"success": false,
"message": err.Error(),
}, err
}
return map[string]interface{}{
"success": result.Success,
"message": result.Message,
"path": result.Path,
"size": result.Size,
}, nil
}
// SelectPDFSaveDirectory 选择PDF保存目录
func (a *App) SelectPDFSaveDirectory() (string, error) {
if a.pdfAPI == nil {
return "", fmt.Errorf("PDF导出功能未初始化")
}
return a.pdfAPI.SelectDirectory()
}
// ExportMarkdownToPDF 使用gofpdf导出Markdown为PDF
func (a *App) ExportMarkdownToPDF(markdownContent string) (string, error) {
// 1. 弹出保存对话框
savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
Title: "保存 PDF",
DefaultFilename: "document.pdf",
Filters: []runtime.FileFilter{
{DisplayName: "PDF 文件", Pattern: "*.pdf"},
},
})
if err != nil || savePath == "" {
return "", err
}
// 2. 创建PDF
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetAutoPageBreak(true, 15)
// 3. 解析Markdown并写入PDF
lines := strings.Split(markdownContent, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "# ") {
// H1 标题
pdf.SetFont("Arial", "B", 24)
pdf.Cell(40, 10, strings.TrimPrefix(line, "# "))
pdf.Ln(12)
} else if strings.HasPrefix(line, "## ") {
// H2 标题
pdf.SetFont("Arial", "B", 18)
pdf.Cell(40, 10, strings.TrimPrefix(line, "## "))
pdf.Ln(10)
} else if strings.HasPrefix(line, "### ") {
// H3 标题
pdf.SetFont("Arial", "B", 14)
pdf.Cell(40, 10, strings.TrimPrefix(line, "### "))
pdf.Ln(8)
} else if strings.HasPrefix(line, "- ") || strings.HasPrefix(line, "* ") {
// 无序列表
pdf.SetFont("Arial", "", 12)
pdf.Cell(10, 7, "•")
pdf.Cell(0, 7, strings.TrimPrefix(line, "- "))
pdf.Ln(7)
} else if strings.HasPrefix(line, "1. ") || strings.HasPrefix(line, "2. ") || strings.HasPrefix(line, "3. ") {
// 有序列表
pdf.SetFont("Arial", "", 12)
pdf.Cell(10, 7, strings.TrimSpace(strings.SplitN(line, ".", 2)[0]) + ".")
pdf.Cell(0, 7, strings.TrimSpace(strings.SplitN(line, ".", 2)[1]))
pdf.Ln(7)
} else if line == "" {
// 空行
pdf.Ln(7)
} else {
// 普通文本
pdf.SetFont("Arial", "", 12)
pdf.MultiCell(190, 7, line, "", "", false)
}
}
// 4. 保存文件
err = pdf.OutputFileAndClose(savePath)
if err != nil {
return "", fmt.Errorf("保存PDF文件失败: %v", err)
}
return savePath, nil
}