数据结构优化

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

View File

@@ -0,0 +1,447 @@
package handlers
import (
"log"
"strconv"
"github.com/gin-gonic/gin"
"github.com/niangaodev/art-code/models"
"github.com/niangaodev/art-code/repositories"
"github.com/niangaodev/art-code/utils"
)
// AdminUploadAttachment 上传附件
func AdminUploadAttachment(c *gin.Context) {
// 获取文件
file, err := c.FormFile("file")
if err != nil {
utils.Error(c, 400, "No file uploaded")
return
}
// 获取分类ID(可选)
categoryIDStr := c.PostForm("categoryId")
var categoryID *uint
if categoryIDStr != "" {
if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
idUint := uint(id)
categoryID = &idUint
}
}
// 获取存储类型
storageType := c.DefaultPostForm("storageType", "local")
// 打开文件
src, err := file.Open()
if err != nil {
utils.Error(c, 500, "Failed to open file")
return
}
defer src.Close()
// 获取OSS配置
var ossConfig *models.OSSConfig
if storageType != "local" {
ossConfig, err = repositories.GetActiveOSSConfig(storageType)
if err != nil || ossConfig == nil {
utils.Error(c, 400, "OSS config not found or not active")
return
}
}
// 创建OSS配置对象
config := &utils.OSSConfig{
StorageType: storageType,
}
if ossConfig != nil {
// 解密AccessKey和SecretKey
accessKey, err := utils.DecryptAES(ossConfig.AccessKey)
if err != nil {
log.Printf("Failed to decrypt access key: %v", err)
utils.Error(c, 500, "Failed to decrypt OSS credentials")
return
}
secretKey, err := utils.DecryptAES(ossConfig.SecretKey)
if err != nil {
log.Printf("Failed to decrypt secret key: %v", err)
utils.Error(c, 500, "Failed to decrypt OSS credentials")
return
}
config.AccessKey = accessKey
config.SecretKey = secretKey
config.Bucket = ossConfig.Bucket
config.Region = ossConfig.Region
config.Domain = ossConfig.Domain
}
// 获取上传器
uploader, err := utils.GetOSSUploader(config)
if err != nil {
utils.Error(c, 500, err.Error())
return
}
// 上传文件
filePath, fileURL, err := uploader.Upload(src, file.Filename, file.Size)
if err != nil {
utils.Error(c, 500, "Failed to upload file: "+err.Error())
return
}
// 获取文件类型
fileType := utils.GetFileType(file.Header.Get("Content-Type"))
// 创建附件记录
attachment := &models.Attachment{
CategoryID: categoryID,
OriginalName: file.Filename,
StoredName: file.Filename,
FilePath: filePath,
FileURL: fileURL,
FileSize: file.Size,
FileType: fileType,
MimeType: file.Header.Get("Content-Type"),
StorageType: storageType,
}
if ossConfig != nil {
ossConfigID := ossConfig.ID
attachment.OSSConfigID = &ossConfigID
}
if err := repositories.CreateAttachment(attachment); err != nil {
// 如果数据库保存失败,尝试删除已上传的文件
uploader.Delete(filePath)
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "File uploaded successfully", attachment)
}
// AdminGetAttachments 获取附件列表
func AdminGetAttachments(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
categoryIDStr := c.Query("categoryId")
fileType := c.Query("fileType")
var categoryID *uint
if categoryIDStr != "" {
if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
idUint := uint(id)
categoryID = &idUint
}
}
attachments, total, err := repositories.GetAttachments(page, pageSize, categoryID, fileType)
if err != nil {
utils.ServerError(c, err)
return
}
res := gin.H{
"list": attachments,
"total": total,
"page": page,
"size": pageSize,
}
utils.Success(c, res)
}
// AdminDeleteAttachment 删除附件
func AdminDeleteAttachment(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid attachment ID")
return
}
// 获取附件信息
attachment, err := repositories.GetAttachmentByID(id)
if err != nil || attachment == nil {
utils.Error(c, 404, "Attachment not found")
return
}
// 获取OSS配置并删除文件
if attachment.StorageType != "" {
var ossConfig *models.OSSConfig
if attachment.OSSConfigID != nil {
ossConfig, err = repositories.GetOSSConfigByID(*attachment.OSSConfigID)
} else {
ossConfig, err = repositories.GetActiveOSSConfig(attachment.StorageType)
}
if err == nil && ossConfig != nil {
config := &utils.OSSConfig{
StorageType: attachment.StorageType,
}
// 解密密钥
if accessKey, err := utils.DecryptAES(ossConfig.AccessKey); err == nil {
config.AccessKey = accessKey
}
if secretKey, err := utils.DecryptAES(ossConfig.SecretKey); err == nil {
config.SecretKey = secretKey
}
config.Bucket = ossConfig.Bucket
config.Region = ossConfig.Region
config.Domain = ossConfig.Domain
uploader, err := utils.GetOSSUploader(config)
if err == nil {
uploader.Delete(attachment.FilePath)
}
}
}
// 删除数据库记录
if err := repositories.DeleteAttachment(id); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Attachment deleted successfully", nil)
}
// AdminGetAttachmentCategories 获取附件分类列表
func AdminGetAttachmentCategories(c *gin.Context) {
categories, err := repositories.GetAttachmentCategories()
if err != nil {
utils.ServerError(c, err)
return
}
utils.Success(c, categories)
}
// AdminCreateAttachmentCategory 创建附件分类
func AdminCreateAttachmentCategory(c *gin.Context) {
var category models.AttachmentCategory
if err := c.ShouldBindJSON(&category); err != nil {
utils.Error(c, 400, "Invalid request")
return
}
if err := repositories.CreateAttachmentCategory(&category); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Category created successfully", category)
}
// AdminUpdateAttachmentCategory 更新附件分类
func AdminUpdateAttachmentCategory(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid category ID")
return
}
var category models.AttachmentCategory
if err := c.ShouldBindJSON(&category); err != nil {
utils.Error(c, 400, "Invalid request")
return
}
category.ID = id
if err := repositories.UpdateAttachmentCategory(&category); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Category updated successfully", nil)
}
// AdminDeleteAttachmentCategory 删除附件分类
func AdminDeleteAttachmentCategory(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid category ID")
return
}
if err := repositories.DeleteAttachmentCategory(id); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Category deleted successfully", nil)
}
// AdminGetOSSConfigs 获取OSS配置列表
func AdminGetOSSConfigs(c *gin.Context) {
configs, err := repositories.GetOSSConfigs()
if err != nil {
utils.ServerError(c, err)
return
}
// 不返回加密的密钥
for i := range configs {
configs[i].AccessKey = "***"
configs[i].SecretKey = "***"
}
utils.Success(c, configs)
}
// AdminCreateOSSConfig 创建OSS配置
func AdminCreateOSSConfig(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
StorageType string `json:"storageType" binding:"required"`
AccessKey string `json:"accessKey" binding:"required"`
SecretKey string `json:"secretKey" binding:"required"`
Bucket string `json:"bucket"`
Region string `json:"region"`
Domain string `json:"domain"`
IsActive int `json:"isActive"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.Error(c, 400, "Invalid request")
return
}
// 加密密钥
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
if err != nil {
utils.Error(c, 500, "Failed to encrypt access key")
return
}
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
if err != nil {
utils.Error(c, 500, "Failed to encrypt secret key")
return
}
ossConfig := &models.OSSConfig{
Name: req.Name,
StorageType: req.StorageType,
AccessKey: encryptedAccessKey,
SecretKey: encryptedSecretKey,
Bucket: req.Bucket,
Region: req.Region,
Domain: req.Domain,
IsActive: req.IsActive,
}
if err := repositories.CreateOSSConfig(ossConfig); err != nil {
utils.ServerError(c, err)
return
}
// 不返回加密的密钥
ossConfig.AccessKey = "***"
ossConfig.SecretKey = "***"
utils.SuccessWithMsg(c, "OSS config created successfully", ossConfig)
}
// AdminUpdateOSSConfig 更新OSS配置
func AdminUpdateOSSConfig(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid OSS config ID")
return
}
var req struct {
Name string `json:"name"`
StorageType string `json:"storageType"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Bucket string `json:"bucket"`
Region string `json:"region"`
Domain string `json:"domain"`
IsActive int `json:"isActive"`
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.Error(c, 400, "Invalid request")
return
}
// 获取现有配置
existingConfig, err := repositories.GetOSSConfigByID(id)
if err != nil || existingConfig == nil {
utils.Error(c, 404, "OSS config not found")
return
}
// 更新字段
if req.Name != "" {
existingConfig.Name = req.Name
}
if req.StorageType != "" {
existingConfig.StorageType = req.StorageType
}
if req.AccessKey != "" && req.AccessKey != "***" {
encryptedAccessKey, err := utils.EncryptAES(req.AccessKey)
if err != nil {
utils.Error(c, 500, "Failed to encrypt access key")
return
}
existingConfig.AccessKey = encryptedAccessKey
}
if req.SecretKey != "" && req.SecretKey != "***" {
encryptedSecretKey, err := utils.EncryptAES(req.SecretKey)
if err != nil {
utils.Error(c, 500, "Failed to encrypt secret key")
return
}
existingConfig.SecretKey = encryptedSecretKey
}
if req.Bucket != "" {
existingConfig.Bucket = req.Bucket
}
if req.Region != "" {
existingConfig.Region = req.Region
}
if req.Domain != "" {
existingConfig.Domain = req.Domain
}
existingConfig.IsActive = req.IsActive
if err := repositories.UpdateOSSConfig(existingConfig); err != nil {
utils.ServerError(c, err)
return
}
// 不返回加密的密钥
existingConfig.AccessKey = "***"
existingConfig.SecretKey = "***"
utils.SuccessWithMsg(c, "OSS config updated successfully", existingConfig)
}
// AdminDeleteOSSConfig 删除OSS配置
func AdminDeleteOSSConfig(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid OSS config ID")
return
}
if err := repositories.DeleteOSSConfig(id); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "OSS config deleted successfully", nil)
}

View File

@@ -2,11 +2,9 @@ package handlers
import (
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/niangaodev/art-code/config"
"github.com/niangaodev/art-code/middleware"
"github.com/niangaodev/art-code/models"
"github.com/niangaodev/art-code/repositories"
"github.com/niangaodev/art-code/utils"
@@ -38,18 +36,13 @@ func Login(c *gin.Context) {
}
// 验证密码
if !utils.CheckPasswordHash(req.Password, user.Password) {
if !utils.CheckPasswordHash(req.Password, user.PasswordHash) {
utils.Error(c, 401, "Invalid username or password")
return
}
// 生成JWT Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"userID": user.ID,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
})
tokenString, err := token.SignedString([]byte(config.JWTSecret))
tokenString, expireUnix, err := middleware.GenerateToken(user.ID, user.Username, user.Role)
if err != nil {
utils.ServerError(c, err)
return
@@ -70,7 +63,8 @@ func Login(c *gin.Context) {
}()
utils.Success(c, gin.H{
"token": tokenString,
"token": tokenString,
"expire": expireUnix,
"user": gin.H{
"id": user.ID,
"username": user.Username,

View File

@@ -38,7 +38,9 @@ func GetColumnByID(c *gin.Context) {
return
}
utils.Success(c, col)
// 构建包含统计信息的响应
response := repositories.BuildColumnResponse(col)
utils.Success(c, response)
}
// GetColumnPosts 获取专栏文章

View File

@@ -17,6 +17,7 @@ func GetPosts(c *gin.Context) {
keyword := c.Query("q")
categoryIDStr := c.Query("category")
tagIDStr := c.Query("tag")
columnIDStr := c.Query("column")
var categoryID uint
if categoryIDStr != "" {
@@ -32,13 +33,37 @@ func GetPosts(c *gin.Context) {
}
}
var columnID uint
if columnIDStr != "" {
if id, err := strconv.ParseUint(columnIDStr, 10, 32); err == nil {
columnID = uint(id)
}
}
// 从数据库获取所有博客文章
posts, err := repositories.GetPosts(keyword, categoryID, tagID)
posts, err := repositories.GetPosts(keyword, categoryID, tagID, columnID)
if err != nil {
utils.ServerError(c, err)
return
}
// 异步记录搜索日志
userIP := c.ClientIP()
userLocation := utils.GetRegion(userIP)
if keyword != "" {
LogSearch(keyword, "keyword", userIP, userLocation)
}
if categoryID > 0 {
LogSearch(strconv.FormatUint(uint64(categoryID), 10), "category", userIP, userLocation)
}
if tagID > 0 {
LogSearch(strconv.FormatUint(uint64(tagID), 10), "tag", userIP, userLocation)
}
if columnID > 0 {
LogSearch(strconv.FormatUint(uint64(columnID), 10), "column", userIP, userLocation)
}
// 构建响应
responses := repositories.BuildPostsResponse(posts)
// Ensure not nil
@@ -304,7 +329,7 @@ func GetPostsByTagID(c *gin.Context) {
return
}
posts, err := repositories.GetPosts("", 0, tagID)
posts, err := repositories.GetPosts("", 0, tagID, 0)
if err != nil {
utils.ServerError(c, err)
return

View File

@@ -0,0 +1,65 @@
package handlers
import (
"strconv"
"github.com/gin-gonic/gin"
"github.com/niangaodev/art-code/models"
"github.com/niangaodev/art-code/repositories"
"github.com/niangaodev/art-code/utils"
)
// AdminGetSearchLogs 获取搜索记录列表(后台)
func AdminGetSearchLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
logs, total, err := repositories.GetSearchLogs(page, pageSize)
if err != nil {
utils.ServerError(c, err)
return
}
res := gin.H{
"list": logs,
"total": total,
"page": page,
"size": pageSize,
}
utils.Success(c, res)
}
// AdminDeleteSearchLog 删除搜索记录
func AdminDeleteSearchLog(c *gin.Context) {
idStr := c.Param("id")
var id uint
if _, err := strconv.ParseUint(idStr, 10, 32); err != nil {
utils.Error(c, 400, "Invalid search log ID")
return
}
if err := repositories.DeleteSearchLog(id); err != nil {
utils.ServerError(c, err)
return
}
utils.SuccessWithMsg(c, "Search log deleted successfully", nil)
}
// LogSearch 记录搜索(异步,不阻塞)
func LogSearch(keyword, searchType, userIP, userLocation string) {
go func() {
logEntry := &models.SearchLog{
Keyword: keyword,
SearchType: searchType,
UserIP: userIP,
UserLocation: userLocation,
}
if err := repositories.CreateSearchLog(logEntry); err != nil {
// Log error but don't fail
// log.Printf("Failed to create search log: %v", err)
}
}()
}