数据结构优化

This commit is contained in:
李琦
2026-01-19 16:14:08 +08:00
parent 6d789c7c2a
commit 68c4c6df1c
36 changed files with 4063 additions and 760 deletions

92
server/utils/encrypt.go Normal file
View File

@@ -0,0 +1,92 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
)
// AESKey 从环境变量或配置中获取,这里使用默认密钥(生产环境应该从配置读取)
var defaultAESKey = []byte("your-32-byte-secret-key-here!!") // 32 bytes for AES-256
// GetAESKey 获取AES密钥应该从配置文件或环境变量读取
func GetAESKey() []byte {
// TODO: 从配置文件或环境变量读取密钥
// key := os.Getenv("AES_ENCRYPTION_KEY")
// if key == "" {
// return defaultAESKey
// }
// return []byte(key)
return defaultAESKey
}
// EncryptAES 使用AES-256-GCM加密数据
func EncryptAES(plaintext string) (string, error) {
key := GetAESKey()
// Create cipher block
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// Create GCM
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Create nonce
nonce := make([]byte, aesGCM.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// Encrypt
ciphertext := aesGCM.Seal(nonce, nonce, []byte(plaintext), nil)
// Encode to base64
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// DecryptAES 使用AES-256-GCM解密数据
func DecryptAES(encrypted string) (string, error) {
key := GetAESKey()
// Decode from base64
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
// Create cipher block
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// Create GCM
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Extract nonce
nonceSize := aesGCM.NonceSize()
if len(ciphertext) < nonceSize {
return "", errors.New("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Decrypt
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}

138
server/utils/oss.go Normal file
View File

@@ -0,0 +1,138 @@
package utils
import (
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
)
// StorageType 存储类型
type StorageType string
const (
StorageLocal StorageType = "local"
StorageQCloud StorageType = "qcloud"
StorageAliyun StorageType = "aliyun"
StorageQiniu StorageType = "qiniu"
)
// OSSConfig OSS配置
type OSSConfig struct {
StorageType string
AccessKey string
SecretKey string
Bucket string
Region string
Domain string
}
// OSSUploader OSS上传接口
type OSSUploader interface {
Upload(file multipart.File, filename string, size int64) (string, string, error) // 返回 filePath, fileURL, error
Delete(filePath string) error
}
// GetOSSUploader 根据存储类型获取上传器
func GetOSSUploader(config *OSSConfig) (OSSUploader, error) {
switch StorageType(config.StorageType) {
case StorageLocal:
return &LocalUploader{
BasePath: "./uploads",
BaseURL: "/uploads",
}, nil
case StorageQCloud:
// TODO: 实现腾讯云COS上传
return nil, fmt.Errorf("qcloud storage not implemented yet")
case StorageAliyun:
// TODO: 实现阿里云OSS上传
return nil, fmt.Errorf("aliyun storage not implemented yet")
case StorageQiniu:
// TODO: 实现七牛云上传
return nil, fmt.Errorf("qiniu storage not implemented yet")
default:
return nil, fmt.Errorf("unsupported storage type: %s", config.StorageType)
}
}
// LocalUploader 本地存储上传器
type LocalUploader struct {
BasePath string
BaseURL string
}
// Upload 上传文件到本地
func (l *LocalUploader) Upload(file multipart.File, filename string, size int64) (string, string, error) {
// 生成唯一文件名
ext := filepath.Ext(filename)
timestamp := time.Now().Unix()
randomStr := fmt.Sprintf("%d", timestamp)
newFilename := fmt.Sprintf("%s_%s%s", strings.TrimSuffix(filename, ext), randomStr, ext)
// 按日期创建目录
dateDir := time.Now().Format("2006/01/02")
uploadDir := filepath.Join(l.BasePath, dateDir)
// 创建目录
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return "", "", fmt.Errorf("failed to create upload directory: %v", err)
}
// 完整文件路径
filePath := filepath.Join(uploadDir, newFilename)
// 创建目标文件
dst, err := os.Create(filePath)
if err != nil {
return "", "", fmt.Errorf("failed to create file: %v", err)
}
defer dst.Close()
// 复制文件内容
if _, err := io.Copy(dst, file); err != nil {
return "", "", fmt.Errorf("failed to copy file: %v", err)
}
// 生成访问URL
fileURL := fmt.Sprintf("%s/%s/%s", l.BaseURL, dateDir, newFilename)
return filePath, fileURL, nil
}
// Delete 删除本地文件
func (l *LocalUploader) Delete(filePath string) error {
// 确保文件路径在BasePath内安全措施
absBasePath, err := filepath.Abs(l.BasePath)
if err != nil {
return err
}
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return err
}
if !strings.HasPrefix(absFilePath, absBasePath) {
return fmt.Errorf("invalid file path: outside base directory")
}
return os.Remove(filePath)
}
// GetFileType 根据MIME类型判断文件类型
func GetFileType(mimeType string) string {
if strings.HasPrefix(mimeType, "image/") {
return "image"
} else if strings.HasPrefix(mimeType, "video/") {
return "video"
} else if strings.HasPrefix(mimeType, "application/pdf") ||
strings.HasPrefix(mimeType, "application/msword") ||
strings.HasPrefix(mimeType, "application/vnd.openxmlformats") ||
strings.HasPrefix(mimeType, "text/") {
return "document"
}
return "other"
}