This commit is contained in:
www.xueximeng.com
2025-06-06 16:09:28 +08:00
parent 679fda7f31
commit 9cf9dce1e3
9 changed files with 381 additions and 258 deletions

View File

@@ -504,10 +504,22 @@ sudo systemctl status nginx
- Teenage Mutant Ninja Turtles忍者神龟
- Carmen Sandiego大神偷卡门
- RWBY红白黑黄
- Avatar: The Last Airbender降世神通最后的气宗
- Avatar: The Legend of Korra降世神通科拉传奇
- Bluey布鲁伊
- Hilda希尔达
- The Owl House猫头鹰魔法社
- Clarence小胖克莱伦斯 / 我爱阿噗)✅
- Heroes of Pure Heart猫猫纯心之谷的英雄们
- Sym-Bionic Titan合神泰坦
- Generator Rex变形小雷 / 机械战士REX
- Di-Gata Defenders迪卡塔卫士
- Over the Garden Wall花园墙外
- The Dragon Prince龙王子
- OK K.O.! Let's Be Heroes成为英雄吧
- Bob's Burgers开心汉堡店
- SpongeBob SquarePants海绵宝宝
- Harley Quinn哈莉·奎茵
- The Owl House猫头鹰魔法社
- Guardians of GaHoole守护者
- Gravity Falls怪诞小镇
- We Bare Bears咱们裸熊
@@ -536,7 +548,6 @@ sudo systemctl status nginx
- Ugly Americans俗世乐土
- Primal史前战纪
- Blue Eye Samurai蓝眼武士
- Hilda希尔达
- HouseBroken一家之主
- Star vs. the Forces of Evil星蝶公主
- The Great North东倒西歪
@@ -544,7 +555,6 @@ sudo systemctl status nginx
- House of Demons恶魔之家
- The Amazing Digital Circus神奇数字马戏团
- Summer Camp Island夏令营岛
- OK K.O.! Let's Be Heroes超级科学伙伴
- The Midnight Gospel午夜福音
- Pantheon万神殿
- Ten Year Old Tom十岁的汤姆
@@ -557,7 +567,6 @@ sudo systemctl status nginx
- My Adventures with Superman我亲爱的怪物伙伴
- Ben 10少年骇客
- She-Ra and the Princesses of Power神勇战士
- Over the Garden Wall花园墙外
- Central Park中央公园
- The Age of the Chip and the Amazing Animals奇波和神奇动物的时代
- Daria拽妹黛薇儿
@@ -574,7 +583,6 @@ sudo systemctl status nginx
- Johnny Bravo强尼布拉沃
- Samurai Jack武士杰克
- Star Wars: The Clone Wars星球大战克隆人战争
- Avatar: The Legend of Korra科拉传奇
- Regular Show普通秀
- Archer间谍亚契
- Final Space终空
@@ -585,8 +593,13 @@ sudo systemctl status nginx
- Space Ghost Coast to Coast太空幽灵海岸到海岸
# 更新日志
- 2020506051132
- 202506061607
✅ 优化悬浮按钮样式问题
✅ 修复最近播放恢复播放失败问题
✅ 新增的资源,如果没有批准任何图片和链接,则代表审核不通过,直接删除该条数据
✅ 修复编辑资源时,将新上传的图片设置为海报失败问题
- 2020506051132
✅ 增加golang版动态生成sitemap工具`sitemap-generator`,为将来容器化做准备
✅ 允许通过环境变量指定assets和数据库路径为将来容器化做准备
✅ 自动判断vite.config.js中是否需要启用`base: '/static/',`,只有正式编译时启用,本地开发不会启用,避免每次编译手动修改一遍

View File

@@ -519,7 +519,7 @@ body {
content: '';
position: absolute;
top: 0;
left: -100%;
left: -170%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);

View File

@@ -506,13 +506,17 @@ export default {
playHistory.value.splice(existingItemIndex, 1);
}
// 获取当前数据源ID
const currentDataSourceId = selectedDataSource.value || '';
// 添加到开头
playHistory.value.unshift({
id: item.id,
title: item.title || '自定义流媒体',
src: item.src,
poster: item.poster || '',
timestamp: new Date().getTime()
timestamp: new Date().getTime(),
dataSourceId: currentDataSourceId // 保存数据源ID
});
// 限制历史记录数量
@@ -530,6 +534,20 @@ export default {
// 播放历史记录中的项目
const playHistoryItem = (item) => {
// 先检查并切换到记录对应的数据源
if (item.dataSourceId && item.dataSourceId !== selectedDataSource.value) {
try {
const dataSourceManager = getDataSourceManager();
if (dataSourceManager.getAllDataSources()[item.dataSourceId]) {
console.log(`切换到历史记录对应的数据源: ${item.dataSourceId}`);
selectedDataSource.value = item.dataSourceId;
dataSourceManager.setCurrentDataSource(item.dataSourceId);
}
} catch (error) {
console.error('切换数据源失败:', error);
}
}
if (item.id) {
// 这是预设的视频
loadStreamById(item.id);

View File

@@ -14,6 +14,7 @@ gobackend/
│ ├── diagnostic/ # 诊断工具
│ │ └── main.go # 数据库诊断程序
│ └── test/ # 测试工具
├── config/ # 配置文件
├── internal/ # 内部包
│ ├── models/ # 数据模型
│ │ ├── models.go # 定义数据模型结构

View File

@@ -1,6 +1,9 @@
package handlers
import (
"dongman/internal/models"
"dongman/internal/utils"
"encoding/json"
"fmt"
"io"
"log"
@@ -8,35 +11,33 @@ import (
"os"
"path/filepath"
"strconv"
"time"
"strings"
"encoding/json"
"github.com/gin-gonic/gin"
"time"
"dongman/internal/models"
"dongman/internal/utils"
"github.com/gin-gonic/gin"
)
// ApproveResource 审批资源 - 仅管理员可访问
func ApproveResource(c *gin.Context) {
// 获取路径参数
resourceID, err := strconv.Atoi(c.Param("id"))
if err != nil {
// 获取资源ID
resourceID, errParse := strconv.Atoi(c.Param("id"))
if errParse != nil {
log.Printf("[ERROR] 无效的资源ID: %v", errParse)
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的资源ID"})
return
}
// 解析请求
var approval models.ResourceApproval
if err := c.ShouldBindJSON(&approval); err != nil {
if errBind := c.ShouldBindJSON(&approval); errBind != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
return
}
// 检查资源是否存在
var resource models.Resource
err = models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if err != nil {
errGet := models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "资源未找到"})
return
}
@@ -60,31 +61,42 @@ func ApproveResource(c *gin.Context) {
// 保存所有新路径
newImagePaths := make([]string, 0, len(approval.ApprovedImages))
if strings.ToLower(string(approval.Status)) == strings.ToLower(string(models.ResourceStatusApproved)){
// 检查是否没有批准任何图片和链接
if len(approval.ApprovedImages) == 0 && len(approval.ApprovedLinks) == 0 {
log.Printf("[INFO] 资源ID: %d 被批准但没有批准任何图片和链接,将直接删除该资源", resourceID)
// 删除资源
_, errDelete := models.DB.Exec(`DELETE FROM resources WHERE id = ?`, resourceID)
if errDelete != nil {
log.Printf("[ERROR] 删除资源失败: %v", errDelete)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("删除资源失败: %v", errDelete)})
return
}
// 返回成功消息
c.JSON(http.StatusOK, gin.H{
"message": "资源已删除,因为没有批准任何图片和链接",
"deleted": true,
"resource_id": resourceID,
})
return
}
// 移动已批准的图片
if len(approval.ApprovedImages) > 0 {
log.Printf("[DEBUG] 开始移动已批准的图片资源ID: %d, 图片数量: %d", resource.ID, len(approval.ApprovedImages))
log.Printf("[DEBUG] 原始图片路径: %v", approval.ApprovedImages)
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
log.Printf("[ERROR] 获取工作目录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取工作目录失败"})
return
}
log.Printf("[DEBUG] 当前工作目录: %s", workDir)
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
// 获取assets目录路径
assetsDir := utils.GetAssetsDir()
log.Printf("[DEBUG] Assets目录路径: %s", assetsDir)
// 创建目标目录
imgsDir := filepath.Join(assetsDir, "imgs", fmt.Sprintf("%d", resourceID))
log.Printf("[DEBUG] 创建目标目录: %s", imgsDir)
if err := os.MkdirAll(imgsDir, 0755); err != nil {
log.Printf("[ERROR] 创建目录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建图片目录失败: %v", err)})
if errMkdir := os.MkdirAll(imgsDir, 0755); errMkdir != nil {
log.Printf("[ERROR] 创建目录失败: %v", errMkdir)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建图片目录失败: %v", errMkdir)})
return
}
@@ -107,7 +119,7 @@ func ApproveResource(c *gin.Context) {
log.Printf("[DEBUG] 移动图片: %s -> %s", sourcePath, destPath)
// 检查源文件是否存在
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
if _, errStat := os.Stat(sourcePath); os.IsNotExist(errStat) {
log.Printf("[ERROR] 源文件不存在: %s", sourcePath)
continue
} else {
@@ -115,40 +127,49 @@ func ApproveResource(c *gin.Context) {
}
// 确保目标目录存在
err = os.MkdirAll(filepath.Dir(destPath), 0755)
if err != nil {
log.Printf("[ERROR] 创建目标目录失败: %v", err)
errDir := os.MkdirAll(filepath.Dir(destPath), 0755)
if errDir != nil {
log.Printf("[ERROR] 创建目标目录失败: %v", errDir)
continue
}
// 移动文件(复制后删除)
// 1. 复制文件
sourceFile, err := os.Open(sourcePath)
if err != nil {
log.Printf("[ERROR] 打开源文件失败: %v", err)
sourceFile, errOpen := os.Open(sourcePath)
if errOpen != nil {
log.Printf("[ERROR] 打开源文件失败: %v", errOpen)
continue
}
destFile, err := os.Create(destPath)
if err != nil {
log.Printf("[ERROR] 创建目标文件失败: %v", err)
sourceFile.Close()
defer sourceFile.Close()
// 创建目标文件
destFile, errCreate := os.Create(destPath)
if errCreate != nil {
log.Printf("[ERROR] 创建目标文件失败: %v", errCreate)
continue
}
defer destFile.Close()
// 复制内容
bytesWritten, err := io.Copy(destFile, sourceFile)
sourceFile.Close()
destFile.Close()
if err != nil {
log.Printf("[ERROR] 复制文件内容失败: %v", err)
_, errCopy := io.Copy(destFile, sourceFile)
if errCopy != nil {
log.Printf("[ERROR] 复制文件内容失败: %v", errCopy)
continue
}
log.Printf("[DEBUG] 复制了 %d 字节数据", bytesWritten)
// 关闭文件以确保所有内容都已写入
errSource := sourceFile.Close()
if errSource != nil {
log.Printf("[ERROR] 关闭源文件失败: %v", errSource)
}
errDest := destFile.Close()
if errDest != nil {
log.Printf("[ERROR] 关闭目标文件失败: %v", errDest)
}
// 验证目标文件已创建
if _, err := os.Stat(destPath); os.IsNotExist(err) {
if _, errStat := os.Stat(destPath); os.IsNotExist(errStat) {
log.Printf("[ERROR] 复制后目标文件不存在: %s", destPath)
continue
} else {
@@ -156,11 +177,11 @@ func ApproveResource(c *gin.Context) {
}
// 2. 删除原文件
if err := os.Remove(sourcePath); err != nil {
log.Printf("[WARN] 删除源文件失败,将重试: %v", err)
if errRemove := os.Remove(sourcePath); errRemove != nil {
log.Printf("[WARN] 删除源文件失败,将重试: %v", errRemove)
time.Sleep(100 * time.Millisecond)
if err := os.Remove(sourcePath); err != nil {
log.Printf("[ERROR] 第二次删除源文件失败: %v", err)
if errRetry := os.Remove(sourcePath); errRetry != nil {
log.Printf("[ERROR] 第二次删除源文件失败: %v", errRetry)
} else {
log.Printf("[DEBUG] 第二次尝试删除源文件成功")
}
@@ -269,7 +290,7 @@ func ApproveResource(c *gin.Context) {
log.Printf("[DEBUG] 资源海报图片: %v", resource.PosterImage)
// approval_records插入审批记录
result, err := models.DB.Exec(
result, errInsert := models.DB.Exec(
`INSERT INTO approval_records (
resource_id, status, field_approvals, field_rejections,
approved_images, rejected_images, poster_image, notes,
@@ -283,18 +304,19 @@ func ApproveResource(c *gin.Context) {
approvalRecord.CreatedAt,
)
if err != nil {
log.Printf("创建审批记录失败: %v", err)
// 继续处理,不要因为审批记录创建失败而中断流程
} else {
id, _ := result.LastInsertId()
log.Printf("已创建审批记录ID: %d", id)
}
if errInsert != nil {
log.Printf("创建审批记录失败: %v", errInsert)
// 继续处理,不要因为审批记录创建失败而中断流程
} else {
id, _ := result.LastInsertId()
log.Printf("已创建审批记录ID: %d", id)
}
// resources 更新资源
_, err = models.DB.Exec(
var errUpdate error
_, errUpdate = models.DB.Exec(
`UPDATE resources SET
status = ?, images = ?, poster_image = ?,
approval_history = ?, updated_at = ?
@@ -303,18 +325,18 @@ func ApproveResource(c *gin.Context) {
resource.ApprovalHistory, resource.UpdatedAt, resource.ID,
)
if err != nil {
log.Printf("[ERROR] 更新资源失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源失败: %v", err)})
if errUpdate != nil {
log.Printf("[ERROR] 更新资源失败: %v", errUpdate)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源失败: %v", errUpdate)})
return
}
log.Printf("[INFO] 成功更新资源ID: %d", resourceID)
// 再次从数据库获取资源,确保返回最新数据
err = models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if err != nil {
log.Printf("警告:获取更新后的资源失败,但资源已更新: %v", err)
errGet = models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
log.Printf("警告:获取更新后的资源失败,但资源已更新: %v", errGet)
}
c.JSON(http.StatusOK, resource)
@@ -437,26 +459,16 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
log.Printf("[DEBUG] 开始移动已批准的补充图片资源ID: %d, 图片数量: %d", resource.ID, len(approval.ApprovedImages))
log.Printf("[DEBUG] 原始图片路径: %v", approval.ApprovedImages)
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
log.Printf("[ERROR] 获取工作目录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取工作目录失败"})
return
}
log.Printf("[DEBUG] 当前工作目录: %s", workDir)
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
// 获取assets目录路径
assetsDir := utils.GetAssetsDir()
log.Printf("[DEBUG] Assets目录路径: %s", assetsDir)
// 创建目标目录
imgsDir := filepath.Join(assetsDir, "imgs", fmt.Sprintf("%d", resourceID))
log.Printf("[DEBUG] 创建目标目录: %s", imgsDir)
if err := os.MkdirAll(imgsDir, 0755); err != nil {
log.Printf("[ERROR] 创建目录失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建图片目录失败: %v", err)})
if errMkdir := os.MkdirAll(imgsDir, 0755); errMkdir != nil {
log.Printf("[ERROR] 创建目录失败: %v", errMkdir)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建图片目录失败: %v", errMkdir)})
return
}
@@ -479,7 +491,7 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
log.Printf("[DEBUG] 移动图片: %s -> %s", sourcePath, destPath)
// 检查源文件是否存在
if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
if _, errStat := os.Stat(sourcePath); os.IsNotExist(errStat) {
log.Printf("[ERROR] 源文件不存在: %s", sourcePath)
continue
} else {
@@ -487,40 +499,49 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
}
// 确保目标目录存在
err = os.MkdirAll(filepath.Dir(destPath), 0755)
if err != nil {
log.Printf("[ERROR] 创建目标目录失败: %v", err)
errDir := os.MkdirAll(filepath.Dir(destPath), 0755)
if errDir != nil {
log.Printf("[ERROR] 创建目标目录失败: %v", errDir)
continue
}
// 移动文件(复制后删除)
// 1. 复制文件
sourceFile, err := os.Open(sourcePath)
if err != nil {
log.Printf("[ERROR] 打开源文件失败: %v", err)
sourceFile, errOpen := os.Open(sourcePath)
if errOpen != nil {
log.Printf("[ERROR] 打开源文件失败: %v", errOpen)
continue
}
destFile, err := os.Create(destPath)
if err != nil {
log.Printf("[ERROR] 创建目标文件失败: %v", err)
sourceFile.Close()
defer sourceFile.Close()
// 创建目标文件
destFile, errCreate := os.Create(destPath)
if errCreate != nil {
log.Printf("[ERROR] 创建目标文件失败: %v", errCreate)
continue
}
defer destFile.Close()
// 复制内容
bytesWritten, err := io.Copy(destFile, sourceFile)
sourceFile.Close()
destFile.Close()
if err != nil {
log.Printf("[ERROR] 复制文件内容失败: %v", err)
_, errCopy := io.Copy(destFile, sourceFile)
if errCopy != nil {
log.Printf("[ERROR] 复制文件内容失败: %v", errCopy)
continue
}
log.Printf("[DEBUG] 复制了 %d 字节数据", bytesWritten)
// 关闭文件以确保所有内容都已写入
errSource := sourceFile.Close()
if errSource != nil {
log.Printf("[ERROR] 关闭源文件失败: %v", errSource)
}
errDest := destFile.Close()
if errDest != nil {
log.Printf("[ERROR] 关闭目标文件失败: %v", errDest)
}
// 验证目标文件已创建
if _, err := os.Stat(destPath); os.IsNotExist(err) {
if _, errStat := os.Stat(destPath); os.IsNotExist(errStat) {
log.Printf("[ERROR] 复制后目标文件不存在: %s", destPath)
continue
} else {
@@ -528,11 +549,11 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
}
// 2. 删除原文件
if err := os.Remove(sourcePath); err != nil {
log.Printf("[WARN] 删除源文件失败,将重试: %v", err)
if errRemove := os.Remove(sourcePath); errRemove != nil {
log.Printf("[WARN] 删除源文件失败,将重试: %v", errRemove)
time.Sleep(100 * time.Millisecond)
if err := os.Remove(sourcePath); err != nil {
log.Printf("[ERROR] 第二次删除源文件失败: %v", err)
if errRetry := os.Remove(sourcePath); errRetry != nil {
log.Printf("[ERROR] 第二次删除源文件失败: %v", errRetry)
} else {
log.Printf("[DEBUG] 第二次尝试删除源文件成功")
}
@@ -655,7 +676,8 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
}
// 更新数据库中的资源信息
_, err := models.DB.Exec(
var errUpdate error
_, errUpdate = models.DB.Exec(
`UPDATE resources SET
images = ?, poster_image = ?, links = ?,
updated_at = ?
@@ -664,9 +686,9 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
time.Now(), resourceID,
)
if err != nil {
log.Printf("[ERROR] 更新资源图片失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源图片失败: %v", err)})
if errUpdate != nil {
log.Printf("[ERROR] 更新资源图片失败: %v", errUpdate)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源图片失败: %v", errUpdate)})
return
}
@@ -679,22 +701,23 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
resource.UpdatedAt = time.Now()
// 更新资源
_, err := models.DB.Exec(
var errUpdate error
_, errUpdate = models.DB.Exec(
`UPDATE resources SET is_supplement_approval = 'True', supplement = NULL, updated_at = ? WHERE id = ?`,
resource.UpdatedAt, resourceID,
)
// 检查错误
if err != nil {
log.Printf("更新资源is_supplement_approval失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源is_supplement_approval失败: %v", err)})
if errUpdate != nil {
log.Printf("更新资源is_supplement_approval失败: %v", errUpdate)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新资源is_supplement_approval失败: %v", errUpdate)})
return
}
log.Printf("资源ID: %d 的is_supplement_approval已成功更新为Truesupplement已清空", resourceID)
// 插入审批记录
result, err := models.DB.Exec(
result, errInsert := models.DB.Exec(
`INSERT INTO approval_records (
resource_id, status, field_approvals, field_rejections,
approved_images, rejected_images, poster_image, notes,
@@ -709,8 +732,8 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
)
if err != nil {
log.Printf("创建补充内容审批记录失败: %v", err)
if errInsert != nil {
log.Printf("创建补充内容审批记录失败: %v", errInsert)
// 继续处理,不要因为审批记录创建失败而中断流程
} else {
id, _ := result.LastInsertId()
@@ -720,9 +743,9 @@ func approveResourceSupplement(c *gin.Context, resourceID int, resource models.R
// 返回更新后的资源
var updatedResource models.Resource
err = models.DB.Get(&updatedResource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if err != nil {
log.Printf("警告:获取更新后的资源失败,但资源已更新: %v", err)
errGet := models.DB.Get(&updatedResource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
log.Printf("警告:获取更新后的资源失败,但资源已更新: %v", errGet)
c.JSON(http.StatusOK, resource)
} else {
c.JSON(http.StatusOK, updatedResource)
@@ -763,18 +786,18 @@ func convertImagesToWebP(imagePaths []string) {
}
// 将路径转换为JSON字符串
pathsJSON, err := json.Marshal(adjustedPaths)
if err != nil {
log.Printf("[ERROR] 无法将图片路径转为JSON: %v", err)
pathsJSON, errJSON := json.Marshal(adjustedPaths)
if errJSON != nil {
log.Printf("[ERROR] 无法将图片路径转为JSON: %v", errJSON)
return
}
log.Printf("[DEBUG] 准备调用WebP转换工具处理以下图片: %s", string(pathsJSON))
// 调用WebP转换工具
resultPaths, err := utils.ConvertMultipleImages(string(pathsJSON), true, false, 4)
if err != nil {
log.Printf("[ERROR] 转换WebP过程中发生错误: %v", err)
resultPaths, errConvert := utils.ConvertMultipleImages(string(pathsJSON), true, false, 4)
if errConvert != nil {
log.Printf("[ERROR] 转换WebP过程中发生错误: %v", errConvert)
return
}
@@ -785,9 +808,9 @@ func convertImagesToWebP(imagePaths []string) {
// DeleteApprovalRecord 删除审批记录 - 仅管理员可访问
func DeleteApprovalRecord(c *gin.Context) {
// 获取路径参数
recordID, err := strconv.Atoi(c.Param("id"))
if err != nil {
log.Printf("解析审批记录ID失败: %v, 参数: %s", err, c.Param("id"))
recordID, errParse := strconv.Atoi(c.Param("id"))
if errParse != nil {
log.Printf("解析审批记录ID失败: %v, 参数: %s", errParse, c.Param("id"))
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的审批记录ID"})
return
}
@@ -796,9 +819,9 @@ func DeleteApprovalRecord(c *gin.Context) {
// 检查记录是否存在
var record models.ApprovalRecord
err = models.DB.Get(&record, `SELECT * FROM approval_records WHERE id = ?`, recordID)
if err != nil {
log.Printf("未找到ID为%d的审批记录: %v", recordID, err)
errGet := models.DB.Get(&record, `SELECT * FROM approval_records WHERE id = ?`, recordID)
if errGet != nil {
log.Printf("未找到ID为%d的审批记录: %v", recordID, errGet)
c.JSON(http.StatusNotFound, gin.H{"error": "未找到审批记录"})
return
}
@@ -806,17 +829,17 @@ func DeleteApprovalRecord(c *gin.Context) {
log.Printf("找到ID为%d的审批记录资源ID: %d", recordID, record.ResourceID)
// 删除记录
result, err := models.DB.Exec(`DELETE FROM approval_records WHERE id = ?`, recordID)
if err != nil {
log.Printf("删除ID为%d的审批记录失败: %v", recordID, err)
result, errDelete := models.DB.Exec(`DELETE FROM approval_records WHERE id = ?`, recordID)
if errDelete != nil {
log.Printf("删除ID为%d的审批记录失败: %v", recordID, errDelete)
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除审批记录失败"})
return
}
// 检查是否真的删除了记录
affected, err := result.RowsAffected()
if err != nil {
log.Printf("获取影响行数失败: %v", err)
affected, errAffected := result.RowsAffected()
if errAffected != nil {
log.Printf("获取影响行数失败: %v", errAffected)
} else if affected == 0 {
log.Printf("ID为%d的审批记录未被删除", recordID)
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除审批记录失败,没有记录被删除"})
@@ -830,23 +853,23 @@ func DeleteApprovalRecord(c *gin.Context) {
// SupplementResource 为资源添加补充内容
func SupplementResource(c *gin.Context) {
// 获取路径参数
resourceID, err := strconv.Atoi(c.Param("id"))
if err != nil {
resourceID, errParse := strconv.Atoi(c.Param("id"))
if errParse != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的资源ID"})
return
}
// 解析请求
var supplement models.SupplementCreate
if err := c.ShouldBindJSON(&supplement); err != nil {
if errBind := c.ShouldBindJSON(&supplement); errBind != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
return
}
// 检查资源是否存在并且是已批准的
var resource models.Resource
err = models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if err != nil {
errGet := models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "资源未找到"})
return
}
@@ -927,13 +950,13 @@ func SupplementResource(c *gin.Context) {
}
// 更新资源,添加补充内容
_, err = models.DB.Exec(
_, errUpdate := models.DB.Exec(
`UPDATE resources SET supplement = ?, is_supplement_approval = ?, updated_at = ? WHERE id = ?`,
supplementData, false, time.Now(), resourceID,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("添加补充内容失败: %v", err)})
if errUpdate != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("添加补充内容失败: %v", errUpdate)})
return
}
@@ -956,13 +979,13 @@ func SupplementResource(c *gin.Context) {
}
// 更新资源,添加补充内容
_, err = models.DB.Exec(
_, errUpdate := models.DB.Exec(
`UPDATE resources SET supplement = ?, is_supplement_approval = ?, updated_at = ? WHERE id = ?`,
supplementData, false, time.Now(), resourceID,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("添加补充内容失败: %v", err)})
if errUpdate != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("添加补充内容失败: %v", errUpdate)})
return
}
@@ -976,16 +999,16 @@ func SupplementResource(c *gin.Context) {
// GetResourceSupplement 获取资源的补充内容 - 仅管理员可访问
func GetResourceSupplement(c *gin.Context) {
// 获取路径参数
resourceID, err := strconv.Atoi(c.Param("id"))
if err != nil {
resourceID, errParse := strconv.Atoi(c.Param("id"))
if errParse != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的资源ID"})
return
}
// 查询资源
var resource models.Resource
err = models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if err != nil {
errGet := models.DB.Get(&resource, `SELECT * FROM resources WHERE id = ?`, resourceID)
if errGet != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "资源未找到"})
return
}
@@ -1006,10 +1029,10 @@ func GetPendingSupplementResources(c *gin.Context) {
// 查询所有包含补充内容的资源
var resources []models.Resource
err := models.DB.Select(&resources,
errSelect := models.DB.Select(&resources,
`SELECT * FROM resources WHERE supplement IS NOT NULL LIMIT ? OFFSET ?`,
limit, skip)
if err != nil {
if errSelect != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询待审批补充内容资源失败"})
return
}
@@ -1048,8 +1071,8 @@ func DeleteApprovalRecords(c *gin.Context) {
IDs []int `json:"ids" binding:"required"`
}
if err := c.ShouldBindJSON(&request); err != nil {
log.Printf("解析请求体失败: %v", err)
if errBind := c.ShouldBindJSON(&request); errBind != nil {
log.Printf("解析请求体失败: %v", errBind)
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数"})
return
}
@@ -1068,23 +1091,23 @@ func DeleteApprovalRecords(c *gin.Context) {
for _, id := range request.IDs {
// 检查记录是否存在
var count int
err := models.DB.Get(&count, `SELECT COUNT(*) FROM approval_records WHERE id = ?`, id)
if err != nil || count == 0 {
errCount := models.DB.Get(&count, `SELECT COUNT(*) FROM approval_records WHERE id = ?`, id)
if errCount != nil || count == 0 {
log.Printf("未找到ID为%d的审批记录", id)
failedIDs = append(failedIDs, id)
continue
}
// 删除记录
result, err := models.DB.Exec(`DELETE FROM approval_records WHERE id = ?`, id)
if err != nil {
log.Printf("删除ID为%d的审批记录失败: %v", id, err)
result, errDelete := models.DB.Exec(`DELETE FROM approval_records WHERE id = ?`, id)
if errDelete != nil {
log.Printf("删除ID为%d的审批记录失败: %v", id, errDelete)
failedIDs = append(failedIDs, id)
continue
}
affected, err := result.RowsAffected()
if err != nil || affected == 0 {
affected, errAffected := result.RowsAffected()
if errAffected != nil || affected == 0 {
log.Printf("ID为%d的审批记录未被删除", id)
failedIDs = append(failedIDs, id)
continue
@@ -1110,9 +1133,9 @@ func GetApprovalRecords(c *gin.Context) {
// 获取审批记录总数
var count int
err := models.DB.Get(&count, "SELECT COUNT(*) FROM approval_records")
if err != nil {
log.Printf("获取审批记录总数失败: %v", err)
errCount := models.DB.Get(&count, "SELECT COUNT(*) FROM approval_records")
if errCount != nil {
log.Printf("获取审批记录总数失败: %v", errCount)
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取审批记录总数失败"})
return
}
@@ -1136,9 +1159,9 @@ func GetApprovalRecords(c *gin.Context) {
LIMIT ? OFFSET ?
`
rows, err := models.DB.Queryx(query, limit, skip)
if err != nil {
log.Printf("查询审批记录失败: %v", err)
rows, errQuery := models.DB.Queryx(query, limit, skip)
if errQuery != nil {
log.Printf("查询审批记录失败: %v", errQuery)
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询审批记录失败"})
return
}
@@ -1156,15 +1179,15 @@ func GetApprovalRecords(c *gin.Context) {
records := []ApprovalRecordResponse{}
for rows.Next() {
var record ApprovalRecordResponse
if err := rows.StructScan(&record); err != nil {
log.Printf("扫描审批记录失败: %v", err)
if errScan := rows.StructScan(&record); errScan != nil {
log.Printf("扫描审批记录失败: %v", errScan)
continue
}
records = append(records, record)
}
if err := rows.Err(); err != nil {
log.Printf("遍历审批记录结果集失败: %v", err)
if errRows := rows.Err(); errRows != nil {
log.Printf("遍历审批记录结果集失败: %v", errRows)
c.JSON(http.StatusInternalServerError, gin.H{"error": "处理审批记录失败"})
return
}
@@ -1179,27 +1202,27 @@ func GetApprovalRecords(c *gin.Context) {
// GetResourceApprovalRecords 获取单个资源的审批记录 - 仅管理员可访问
func GetResourceApprovalRecords(c *gin.Context) {
// 获取资源ID
resourceID, err := strconv.Atoi(c.Param("id"))
if err != nil {
log.Printf("无效的资源ID: %v", err)
resourceID, errParse := strconv.Atoi(c.Param("id"))
if errParse != nil {
log.Printf("无效的资源ID: %v", errParse)
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的资源ID"})
return
}
// 检查资源是否存在
var resource models.Resource
err = models.DB.Get(&resource, "SELECT * FROM resources WHERE id = ?", resourceID)
if err != nil {
log.Printf("资源未找到: %v", err)
errGet := models.DB.Get(&resource, "SELECT * FROM resources WHERE id = ?", resourceID)
if errGet != nil {
log.Printf("资源未找到: %v", errGet)
c.JSON(http.StatusNotFound, gin.H{"error": "资源未找到"})
return
}
// 查询该资源的审批记录
var records []models.ApprovalRecord
err = models.DB.Select(&records, "SELECT * FROM approval_records WHERE resource_id = ? ORDER BY created_at DESC", resourceID)
if err != nil {
log.Printf("查询资源审批记录失败: %v", err)
errSelect := models.DB.Select(&records, "SELECT * FROM approval_records WHERE resource_id = ? ORDER BY created_at DESC", resourceID)
if errSelect != nil {
log.Printf("查询资源审批记录失败: %v", errSelect)
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询资源审批记录失败"})
return
}
@@ -1209,4 +1232,4 @@ func GetResourceApprovalRecords(c *gin.Context) {
"resource": resource,
"records": records,
})
}
}

View File

@@ -560,6 +560,17 @@ func UpdateResource(c *gin.Context) {
log.Printf("处理图片更新,收到 %d 张图片", len(resourceUpdate.Images))
// 检查图片是否在临时目录中,如果是则移动到永久目录
imagesToMove := make([]string, 0)
var posterImageInUpload bool
var posterImageOriginalPath string
// 检查海报图片是否在待上传图片中
if resourceUpdate.PosterImage != nil && *resourceUpdate.PosterImage != "" &&
strings.Contains(*resourceUpdate.PosterImage, "/assets/uploads/") {
posterImageInUpload = true
posterImageOriginalPath = *resourceUpdate.PosterImage
log.Printf("海报图片在上传图片中: %s", posterImageOriginalPath)
}
for _, img := range resourceUpdate.Images {
// 检查图片是否在uploads目录中
if strings.Contains(img, "/assets/uploads/") {
@@ -579,6 +590,21 @@ func UpdateResource(c *gin.Context) {
}
log.Printf("图片移动完成,新路径: %v", newImagePaths)
// 如果海报图片在待移动图片中,更新其路径
if posterImageInUpload {
// 在移动后的图片中查找对应海报的新路径
for i, oldPath := range imagesToMove {
if oldPath == posterImageOriginalPath {
if i < len(newImagePaths) {
newPosterPath := newImagePaths[i]
log.Printf("更新海报图片路径: %s -> %s", posterImageOriginalPath, newPosterPath)
resource.PosterImage = &newPosterPath
}
break
}
}
}
// 更新图片路径(保留不需要移动的图片)
finalImages := make([]string, 0)
@@ -598,6 +624,19 @@ func UpdateResource(c *gin.Context) {
// 发生错误时继续使用原始图片
} else {
finalImages = webpImages
// 如果海报图片被转换为WebP也需要更新海报路径
if posterImageInUpload && resource.PosterImage != nil {
originalPosterPath := *resource.PosterImage
for i, oldPath := range finalImages {
if oldPath == originalPosterPath && i < len(webpImages) {
webpPosterPath := webpImages[i]
log.Printf("更新海报图片WebP路径: %s -> %s", originalPosterPath, webpPosterPath)
resource.PosterImage = &webpPosterPath
break
}
}
}
}
resource.Images = finalImages
@@ -616,33 +655,19 @@ func UpdateResource(c *gin.Context) {
}
}
// 处理海报图片更新
if resourceUpdate.PosterImage != nil {
// 处理海报图片更新,但仅当它不是来自上述已处理的上传图片
if resourceUpdate.PosterImage != nil && !strings.Contains(*resourceUpdate.PosterImage, "/assets/uploads/") {
log.Printf("处理海报图片更新: %s", *resourceUpdate.PosterImage)
if *resourceUpdate.PosterImage != "" {
// 检查海报图片是否在临时目录中
if strings.Contains(*resourceUpdate.PosterImage, "/assets/uploads/") {
log.Printf("海报图片需要移动: %s", *resourceUpdate.PosterImage)
// 移动海报图片到永久目录
newPosterPath, err := utils.MoveApprovedImage(resourceID, *resourceUpdate.PosterImage)
if err != nil {
log.Printf("移动海报图片失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("移动海报图片失败: %v", err)})
return
}
log.Printf("海报图片移动完成,新路径: %s", newPosterPath)
resource.PosterImage = &newPosterPath
} else {
log.Printf("海报图片无需移动")
resource.PosterImage = resourceUpdate.PosterImage
}
// 无需再次移动已经处理过的上传图片
resource.PosterImage = resourceUpdate.PosterImage
} else {
log.Printf("清除海报图片设置")
resource.PosterImage = resourceUpdate.PosterImage
}
updated = true
} else {
log.Printf("未更新海报图片")
log.Printf("海报图片已在图片处理逻辑中处理或未更新")
}
if resourceUpdate.Links != nil {

View File

@@ -4,13 +4,13 @@ import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt"
"dongman/internal/utils"
)
// DB 是全局数据库连接
@@ -75,16 +75,10 @@ CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
// InitDB 初始化数据库连接
func InitDB() (*sqlx.DB, error) {
// 获取当前工作目录
workDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("获取工作目录失败: %w", err)
}
// 数据库文件路径
dbPath := filepath.Join(workDir, "resource_hub.db")
log.Printf("使用数据库: %s", dbPath)
// 从utils包获取数据库路径
dbPath := utils.GetDbPath()
log.Printf("连接数据库: %s", dbPath)
// 连接SQLite数据库
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("file:%s?_journal=WAL&_foreign_keys=on", dbPath))
if err != nil {
@@ -161,15 +155,9 @@ func RestoreImagesPath() error {
return fmt.Errorf("查询资源失败: %w", err)
}
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("获取工作目录失败: %w", err)
}
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
log.Printf("assets目录: %s", assetsDir)
// 获取资源目录
assetsDir := utils.GetAssetsDir()
log.Printf("资源目录: %s", assetsDir)
// 扫描所有图片文件
allImages := make(map[string]string)

View File

@@ -0,0 +1,73 @@
package utils
import (
"log"
"os"
"path/filepath"
)
var (
// 全局配置变量
DbPath string
AssetsDir string
)
// 初始化配置
func init() {
// 初始化数据库路径
if envPath := os.Getenv("DB_PATH"); envPath != "" {
DbPath = envPath
log.Printf("使用环境变量指定的数据库路径: %s", DbPath)
} else {
// 获取当前工作目录
workDir, err := os.Getwd()
if err != nil {
log.Printf("获取工作目录失败: %v使用默认路径", err)
workDir = "."
}
// 使用默认数据库文件路径
DbPath = filepath.Join(workDir, "resource_hub.db")
log.Printf("使用默认数据库路径: %s", DbPath)
}
// 初始化资源目录
if envPath := os.Getenv("ASSETS_PATH"); envPath != "" {
AssetsDir = envPath
log.Printf("使用环境变量指定的资源目录: %s", AssetsDir)
} else {
// 获取当前工作目录
workDir, err := os.Getwd()
if err != nil {
log.Printf("获取工作目录失败: %v使用默认路径", err)
workDir = "."
}
// 使用默认资源目录路径
AssetsDir = filepath.Join(workDir, "..", "assets")
log.Printf("使用默认资源目录: %s", AssetsDir)
}
// 确保目录存在
ensureDirExists(filepath.Dir(DbPath))
ensureDirExists(AssetsDir)
ensureDirExists(filepath.Join(AssetsDir, "uploads"))
ensureDirExists(filepath.Join(AssetsDir, "imgs"))
}
// 确保目录存在
func ensureDirExists(dir string) {
if err := os.MkdirAll(dir, 0755); err != nil {
log.Printf("创建目录失败 %s: %v", dir, err)
}
}
// GetAssetsDir 获取资源目录路径
func GetAssetsDir() string {
return AssetsDir
}
// GetDbPath 获取数据库路径
func GetDbPath() string {
return DbPath
}

View File

@@ -71,20 +71,14 @@ func SaveUploadedFile(file io.Reader, filename string) (string, error) {
return filepath.Join("/assets/uploads", time.Now().Format("20060102"), uniqueFilename), nil
}
// MoveApprovedImages 将图片从uploads目录移动到imgs/resourceID目录
// MoveApprovedImages 移动已批准的图片到资源目录
func MoveApprovedImages(resourceID int, imagePaths []string) ([]string, error) {
if len(imagePaths) == 0 {
return []string{}, nil
}
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("获取工作目录失败: %w", err)
}
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
// 获取资源目录
assetsDir := GetAssetsDir()
// 创建目标目录
imgsDir := filepath.Join(assetsDir, "imgs", fmt.Sprintf("%d", resourceID))
@@ -138,14 +132,8 @@ func MoveApprovedImage(resourceID int, imagePath string) (string, error) {
return "", nil
}
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("获取工作目录失败: %w", err)
}
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
// 获取资源目录
assetsDir := GetAssetsDir()
// 创建目标目录
imgsDir := filepath.Join(assetsDir, "imgs", fmt.Sprintf("%d", resourceID))
@@ -227,14 +215,8 @@ func moveFile(src, dst string) error {
// ensureUploadDir 确保上传目录存在
func ensureUploadDir() (string, error) {
// 获取工作目录
workDir, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("获取工作目录失败: %w", err)
}
// 构建assets目录路径
assetsDir := filepath.Join(workDir, "..", "assets")
// 获取资源目录
assetsDir := GetAssetsDir()
// 按日期创建上传目录
dateDir := time.Now().Format("20060102")