重构:文件系统模块化架构,增强 Markdown 渲染
- 拆分 FileSystem.vue 为模块化组件架构 - 新增 Markdown Mermaid 图表渲染支持 - 新增 180+ 编程语言代码高亮 - 修复编辑/预览模式切换渲染问题 - 优化亮色/暗色模式主题适配 - 新增 TypeScript 类型定义
This commit is contained in:
@@ -7,9 +7,9 @@ import (
|
||||
|
||||
"u-desk/internal/common"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
// MongoClient MongoDB 客户端
|
||||
@@ -111,11 +111,12 @@ func tryConnectMongo(config *MongoConfig, authSource, authMechanism string) (*Mo
|
||||
SetConnectTimeout(common.TimeoutConnect).
|
||||
SetServerSelectionTimeout(common.TimeoutConnect)
|
||||
|
||||
// 创建客户端
|
||||
// 创建客户端 (v2: 移除了 context 参数)
|
||||
client, err := mongo.Connect(clientOptions)
|
||||
|
||||
// 创建 context 用于其他操作
|
||||
ctx, cancel := context.WithTimeout(context.Background(), common.TimeoutConnect)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, clientOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("连接 MongoDB 失败: %v", err)
|
||||
}
|
||||
@@ -659,14 +660,17 @@ func (c *MongoClient) PreviewCollectionIndexes(ctx context.Context, database, co
|
||||
continue
|
||||
}
|
||||
|
||||
// 构建索引选项
|
||||
// 构建索引选项,并跟踪 unique 状态(v2: IndexOptionsBuilder 无 Unique 字段可读)
|
||||
indexOptions := options.Index()
|
||||
indexOptions.SetName(name)
|
||||
|
||||
isUnique := false
|
||||
if unique, ok := idx["unique"].(bool); ok && unique {
|
||||
indexOptions.SetUnique(true)
|
||||
isUnique = true
|
||||
} else if nonUnique, ok := idx["Non_unique"].(float64); ok && nonUnique == 0 {
|
||||
indexOptions.SetUnique(true)
|
||||
isUnique = true
|
||||
}
|
||||
|
||||
// 如果索引已存在,先删除再创建
|
||||
@@ -686,7 +690,7 @@ func (c *MongoClient) PreviewCollectionIndexes(ctx context.Context, database, co
|
||||
keysStr += "}"
|
||||
|
||||
optionsStr := "{name: \"" + name + "\""
|
||||
if indexOptions.Unique != nil && *indexOptions.Unique {
|
||||
if isUnique {
|
||||
optionsStr += ", unique: true"
|
||||
}
|
||||
optionsStr += "}"
|
||||
@@ -748,7 +752,8 @@ func (c *MongoClient) UpdateCollectionIndexes(ctx context.Context, database, col
|
||||
// 删除不存在的索引
|
||||
for name := range currentIndexMap {
|
||||
if !newIndexMap[name] {
|
||||
_, err := coll.Indexes().DropOne(ctx, name)
|
||||
// v2: DropOne 只返回 error,不再返回 bson.Raw
|
||||
err := coll.Indexes().DropOne(ctx, name)
|
||||
if err != nil {
|
||||
return commands, fmt.Errorf("删除索引失败: %v, 索引名: %s", err, name)
|
||||
}
|
||||
@@ -803,7 +808,8 @@ func (c *MongoClient) UpdateCollectionIndexes(ctx context.Context, database, col
|
||||
|
||||
// 如果索引已存在,先删除再创建
|
||||
if currentIndexMap[name] {
|
||||
_, err := coll.Indexes().DropOne(ctx, name)
|
||||
// v2: DropOne 只返回 error,不再返回 bson.Raw
|
||||
err := coll.Indexes().DropOne(ctx, name)
|
||||
if err != nil {
|
||||
return commands, fmt.Errorf("删除旧索引失败: %v, 索引名: %s", err, name)
|
||||
}
|
||||
|
||||
133
internal/filesystem/content_detector.go
Normal file
133
internal/filesystem/content_detector.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const maxDetectSize = 500 * 1024 // 500KB
|
||||
|
||||
// FileTypeInfo 文件类型信息
|
||||
type FileTypeInfo struct {
|
||||
Extension string `json:"extension"`
|
||||
Category string `json:"category"` // image, text, binary
|
||||
MIMEType string `json:"mime_type"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
|
||||
// 常见文件魔数
|
||||
var magicNumbers = []struct {
|
||||
magic []byte
|
||||
ext string
|
||||
category string
|
||||
mime string
|
||||
}{
|
||||
// 图片
|
||||
{[]byte{0xFF, 0xD8, 0xFF}, "jpg", "image", "image/jpeg"},
|
||||
{[]byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}, "png", "image", "image/png"},
|
||||
{[]byte{0x47, 0x49, 0x46, 0x38}, "gif", "image", "image/gif"},
|
||||
{[]byte{0x42, 0x4D}, "bmp", "image", "image/bmp"},
|
||||
{[]byte{0x57, 0x45, 0x42, 0x50}, "webp", "image", "image/webp"},
|
||||
|
||||
// 文档
|
||||
{[]byte{0x25, 0x50, 0x44, 0x46}, "pdf", "pdf", "application/pdf"},
|
||||
|
||||
// 压缩
|
||||
{[]byte{0x50, 0x4B, 0x03, 0x04}, "zip", "archive", "application/zip"},
|
||||
}
|
||||
|
||||
// DetectFileTypeByContent 通过文件内容检测文件类型
|
||||
func (s *FileSystemService) DetectFileTypeByContent(path string) (*FileTypeInfo, error) {
|
||||
if err := s.validatePath(path); err != nil {
|
||||
return nil, fmt.Errorf("路径验证失败: %w", err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法访问文件: %w", err)
|
||||
}
|
||||
|
||||
if info.Size() > maxDetectSize {
|
||||
return &FileTypeInfo{Category: "unknown", Confidence: 0}, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 检测魔数
|
||||
for _, m := range magicNumbers {
|
||||
if len(data) >= len(m.magic) && bytes.Equal(data[:len(m.magic)], m.magic) {
|
||||
return &FileTypeInfo{
|
||||
Extension: m.ext,
|
||||
Category: m.category,
|
||||
MIMEType: m.mime,
|
||||
Confidence: 0.95,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否为文本
|
||||
if isTextContent(data) {
|
||||
return &FileTypeInfo{
|
||||
Extension: "txt",
|
||||
Category: "text",
|
||||
MIMEType: "text/plain",
|
||||
Confidence: 0.8,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &FileTypeInfo{
|
||||
Extension: "",
|
||||
Category: "binary",
|
||||
MIMEType: "application/octet-stream",
|
||||
Confidence: 0.5,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isTextContent 检测是否为文本内容
|
||||
func isTextContent(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
textBytes := 0
|
||||
for _, b := range data[:min(len(data), 512)] {
|
||||
if b == 9 || b == 10 || b == 13 || (b >= 32 && b <= 126) {
|
||||
textBytes++
|
||||
} else if b == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return float64(textBytes)/float64(min(len(data), 512)) > 0.9
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// DetectFileTypeByContentSimple 简化接口
|
||||
func DetectFileTypeByContentSimple(path string) (map[string]interface{}, error) {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := service.DetectFileTypeByContent(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"extension": info.Extension,
|
||||
"category": info.Category,
|
||||
"mime_type": info.MIMEType,
|
||||
"confidence": info.Confidence,
|
||||
}, nil
|
||||
}
|
||||
@@ -122,15 +122,13 @@ func (v *DefaultPathValidator) checkWindowsSystemPaths(path string) *ValidationE
|
||||
if len(lowerPath) >= 3 && lowerPath[1] == ':' {
|
||||
driveLetter := lowerPath[0:1]
|
||||
|
||||
// 检查系统关键目录
|
||||
// 检查系统关键目录(仅保留最关键的系统目录)
|
||||
forbiddenDirs := []string{
|
||||
driveLetter + ":\\windows",
|
||||
driveLetter + ":\\program files",
|
||||
driveLetter + ":\\program files (x86)",
|
||||
driveLetter + ":\\program files (arm)",
|
||||
driveLetter + ":\\programdata",
|
||||
driveLetter + ":\\system volume information",
|
||||
driveLetter + ":\\recovery",
|
||||
driveLetter + ":\\boot",
|
||||
}
|
||||
|
||||
@@ -138,7 +136,7 @@ func (v *DefaultPathValidator) checkWindowsSystemPaths(path string) *ValidationE
|
||||
if strings.HasPrefix(lowerPath, fb) {
|
||||
return &ValidationError{
|
||||
Path: path,
|
||||
Reason: fmt.Sprintf("禁止访问系统目录: %s", fb),
|
||||
Reason: "禁止访问系统关键目录",
|
||||
IsError: true,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user