update: schema

This commit is contained in:
ctwj
2025-07-18 00:34:27 +08:00
parent 043d66e183
commit dbc73b7491
11 changed files with 531 additions and 229 deletions

View File

@@ -16,7 +16,7 @@
### 1. Fork 项目
1. 访问 [L9Pan GitHub 仓库](https://github.com/ctwj/panResManage)
1. 访问 [GitHub 仓库](https://github.com/ctwj/panResManage)
2. 点击右上角的 Fork 按钮
3. 选择您的 GitHub 账户

View File

@@ -1,13 +1,31 @@
# 多阶段构建
# 前端构建阶段
FROM node:18-alpine AS frontend-builder
# 安装pnpm
RUN npm install -g pnpm
WORKDIR /app/web
COPY web/package*.json ./
RUN npm ci --only=production
COPY web/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY web/ ./
RUN npm run build
RUN pnpm run build
# 前端运行阶段
FROM node:18-alpine AS frontend
RUN npm install -g pnpm
WORKDIR /app
COPY --from=frontend-builder /app/web/.output ./.output
COPY --from=frontend-builder /app/web/package*.json ./
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
# 后端构建阶段
FROM golang:1.21-alpine AS backend-builder
WORKDIR /app
@@ -17,7 +35,8 @@ RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM alpine:latest
# 后端运行阶段
FROM alpine:latest AS backend
RUN apk --no-cache add ca-certificates
@@ -26,9 +45,6 @@ WORKDIR /root/
# 复制后端二进制文件
COPY --from=backend-builder /app/main .
# 复制前端构建文件
COPY --from=frontend-builder /app/web/.output /root/web
# 创建uploads目录
RUN mkdir -p uploads

View File

@@ -5,10 +5,10 @@
![Go Version](https://img.shields.io/badge/Go-1230?logo=go&logoColor=white)
![Vue Version](https://img.shields.io/badge/Vue-334FC08D?logo=vue.js&logoColor=white)
![Nuxt Version](https://img.shields.io/badge/Nuxt-300.8+-00DC82?logo=nuxt.js&logoColor=white)
![License](https://img.shields.io/badge/License-MIT-yellow.svg)
![License](https://img.shields.io/badge/License-GPL%20v3-blue.svg)
![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791go=postgresql&logoColor=white)
**一个现代化的网盘资源管理系统,支持多平台网盘资源统一管理和分享**
**一个现代化的网盘资源管理系统,支持多网盘自动化转存分享**
🌐 [在线演示](#) | 📖 [文档](#) | 🐛 问题反馈](#) | ⭐ [给个星标](#)
@@ -16,13 +16,26 @@
---
## 🔔 温馨提示
📌 **本项目仅供技术交流与学习使用**,自身不存储或提供任何资源文件及下载链接。
📌 **请勿将本项目用于任何违法用途**,否则后果自负。
📌 如有任何问题或建议,欢迎交流探讨! 😊
> **免责声明**:本项目由 Trae AI 辅助编写。由于时间有限,仅在空闲时维护。如遇使用问题,请优先自行排查,感谢理解!
---
## ✨ 功能特性
### 🎯 核心功能
- **📁 多平台网盘支持** - 支持夸克网盘、阿里云盘、百度网盘、UC网盘
- **🔍 智能搜索** - 全文搜索、标签筛选、分类浏览
- **📊 数据统计** - 资源统计、搜索分析、热门关键词
- **🏷️ 标签系统** - 灵活的标签管理和资源分类
- **🔍 公开API** - 支持API数据录入资源搜索
- **🏷️ 自动预处理** - 系统自动处理资源, 对数据进行有效性判断
- **📊 自动转存分享** - 有效资源,如果属于支持类型将自动转存分享
- **📱 多账号管理** - 同平台支持多账号管理
### 🛠️ 管理功能
- **📦 批量操作** - 批量添加、导入、管理资源
@@ -48,7 +61,7 @@
- **🔐 JWT** - 身份认证
### 前端技术栈
- **⚡ Nuxt.js 3 - Vue.js全栈框架
- **⚡ Nuxt.js 3** - Vue.js全栈框架
- **🎨 Vue 3** - 渐进式JavaScript框架
- **📝 TypeScript** - 类型安全的JavaScript
- **🎨 Tailwind CSS** - 实用优先的CSS框架
@@ -65,24 +78,37 @@
### 环境要求
- **Go**1.23+
- **Node.js** 18+
- **PostgreSQL** 15+
- **pnpm** (推荐) 或 npm
- **Docker** 和 **Docker Compose**
- 或者本地环境:
- **Go** 1.23+
- **Node.js** 18+
- **PostgreSQL** 15+
- **pnpm** (推荐) 或 npm
### 方式一Docker 部署(推荐)
#### 使用启动脚本(最简单)
```bash
# 克隆项目
git clone https://github.com/ctwj/panResManage.git
cd panResManagepan.git
cd l9pan
cd panResManage
# 使用启动脚本
./docker-start.sh
```
#### 手动启动
```bash
# 克隆项目
git clone https://github.com/ctwj/panResManage.git
cd panResManage
# 使用 Docker Compose 启动
docker-compose up -d
docker compose up --build -d
# 访问应用
open http://localhost:8080
# 前端: http://localhost:3000
# 后端API: http://localhost:8080
```
### 方式二:本地开发
@@ -93,7 +119,7 @@ git clone https://github.com/ctwj/panResManage.git
cd panResManage
```
#### 2 后端设置
#### 2. 后端设置
```bash
# 复制环境变量文件
cp env.example .env
@@ -108,7 +134,7 @@ go mod tidy
go run main.go
```
#### 3 前端设置
#### 3. 前端设置
```bash
# 进入前端目录
cd web
@@ -176,6 +202,14 @@ DB_NAME=res_db
PORT=8080
```
### Docker 服务说明
| 服务 | 端口 | 说明 |
|------|------|------|
| frontend | 3000 | Nuxt.js 前端应用 |
| backend | 8080 | Go API 后端服务 |
| postgres | 5432 | PostgreSQL 数据库 |
### 支持的网盘平台
| 平台 | 状态 | 功能 |

198
dock
View File

@@ -1,198 +0,0 @@
# 数据库重构说明
## 概述
本次重构将原有的基于原生SQL的数据库操作改为使用GORM ORM框架并采用了Repository模式和分层架构。
## 新的目录结构
```
db/
├── connection.go # 数据库连接管理
├── entity/ # 实体模型
│ ├── pan.go
│ ├── cks.go
│ ├── category.go
│ ├── tag.go
│ ├── resource.go
│ ├── resource_tag.go
│ └── ready_resource.go
├── repo/ # Repository层
│ ├── base.go # 基础Repository接口和实现
│ ├── pan_repository.go
│ ├── cks_repository.go
│ ├── resource_repository.go
│ ├── category_repository.go
│ ├── tag_repository.go
│ ├── ready_resource_repository.go
│ └── manager.go # Repository管理器
├── dto/ # 数据传输对象
│ ├── request.go # 请求DTO
│ └── response.go # 响应DTO
└── converter/ # 转换器
└── converter.go # 实体与DTO转换
```
## 主要改进
### 1. 使用GORM ORM
- 自动迁移表结构
- 关联关系管理
- 软删除支持
- 事务处理
### 2. Repository模式
- 抽象数据访问层
- 统一的CRUD操作接口
- 业务逻辑与数据访问分离
### 3. 分层架构
- Entity层数据模型
- Repository层数据访问
- DTO层数据传输
- Converter层数据转换
- Handler层业务逻辑
### 4. 类型安全
- 使用泛型实现通用Repository
- 强类型的请求和响应对象
- 统一的错误处理
## 实体模型
### Pan (平台)
```go
type Pan struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Name string `json:"name" gorm:"size:64;comment:平台名称"`
Key int `json:"key" gorm:"comment:平台标识"`
Ck string `json:"ck" gorm:"type:text;comment:cookie"`
IsValid bool `json:"is_valid" gorm:"default:true;comment:是否有效"`
Space int64 `json:"space" gorm:"default:0;comment:总空间"`
LeftSpace int64 `json:"left_space" gorm:"default:0;comment:剩余空间"`
Remark string `json:"remark" gorm:"size:64;not null;comment:备注"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
// 关联关系
Cks []Cks `json:"cks" gorm:"foreignKey:PanID"`
}
```
### Resource (资源)
```go
type Resource struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
Title string `json:"title" gorm:"size:255;not null;comment:资源标题"`
Description string `json:"description" gorm:"type:text;comment:资源描述"`
URL string `json:"url" gorm:"size:128;comment:资源链接"`
PanID *uint `json:"pan_id" gorm:"comment:平台ID"`
QuarkURL string `json:"quark_url" gorm:"size:500;comment:夸克链接"`
FileSize string `json:"file_size" gorm:"size:100;comment:文件大小"`
CategoryID *uint `json:"category_id" gorm:"comment:分类ID"`
ViewCount int `json:"view_count" gorm:"default:0;comment:浏览次数"`
IsValid bool `json:"is_valid" gorm:"default:true;comment:是否有效"`
IsPublic bool `json:"is_public" gorm:"default:true;comment:是否公开"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
// 关联关系
Category Category `json:"category" gorm:"foreignKey:CategoryID"`
Pan Pan `json:"pan" gorm:"foreignKey:PanID"`
Tags []Tag `json:"tags" gorm:"many2many:resource_tags;"`
}
```
## Repository接口
### 基础Repository
```go
type BaseRepository[T any] interface {
Create(entity *T) error
FindByID(id uint) (*T, error)
FindAll() ([]T, error)
Update(entity *T) error
Delete(id uint) error
FindWithPagination(page, limit int) ([]T, int64, error)
}
```
### 资源Repository
```go
type ResourceRepository interface {
BaseRepository[entity.Resource]
FindWithRelations() ([]entity.Resource, error)
FindByCategoryID(categoryID uint) ([]entity.Resource, error)
FindByPanID(panID uint) ([]entity.Resource, error)
FindByIsValid(isValid bool) ([]entity.Resource, error)
FindByIsPublic(isPublic bool) ([]entity.Resource, error)
Search(query string, categoryID *uint, page, limit int) ([]entity.Resource, int64, error)
IncrementViewCount(id uint) error
FindWithTags() ([]entity.Resource, error)
UpdateWithTags(resource *entity.Resource, tagIDs []uint) error
}
```
## 使用示例
### 初始化数据库
```go
// 在main.go中
if err := db.InitDB(); err != nil {
log.Fatal("数据库连接失败:", err)
}
// 创建Repository管理器
repoManager := db.NewRepositoryManager(db.DB)
handlers.SetRepositoryManager(repoManager)
```
### 在Handler中使用
```go
// 获取资源列表
resources, err := repoManager.ResourceRepository.FindWithRelations()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 转换为响应DTO
responses := converter.ToResourceResponseList(resources)
c.JSON(http.StatusOK, responses)
```
## 迁移指南
### 1. 更新依赖
```bash
go get -u gorm.io/gorm gorm.io/driver/postgres
```
### 2. 更新导入
将 `res_db/models` 替换为 `res_db/db`
### 3. 更新Handler
使用新的Repository接口替代原生SQL查询
### 4. 测试
确保所有API端点正常工作
## 优势
1. **代码复用**通用Repository减少重复代码
2. **类型安全**:编译时检查,减少运行时错误
3. **易于维护**:清晰的分层结构
4. **自动迁移**GORM自动处理表结构变更
5. **关联查询**:自动处理复杂的关联关系
6. **软删除**:支持数据恢复
7. **事务支持**:自动事务管理
## 注意事项
1. 确保数据库连接配置正确
2. 首次运行会自动创建表结构
3. 软删除的记录不会真正删除
4. 关联查询可能影响性能,需要合理使用
5. 大量数据时需要考虑分页优化

View File

@@ -17,8 +17,10 @@ services:
timeout: 5s
retries: 5
app:
build: .
backend:
build:
context: .
target: backend
ports:
- "8080:8080"
environment:
@@ -34,5 +36,16 @@ services:
volumes:
- ./uploads:/root/uploads
frontend:
build:
context: .
target: frontend
ports:
- "3000:3000"
environment:
API_BASE: http://localhost:8080/api
depends_on:
- backend
volumes:
postgres_data:

46
docker-start.sh Executable file
View File

@@ -0,0 +1,46 @@
#!/bin/bash
echo "🚀 启动网盘资源管理系统..."
# 检查Docker是否运行
if ! docker info > /dev/null 2>&1; then
echo "❌ Docker未运行请先启动Docker"
exit 1
fi
# 检测Docker Compose命令
if command -v docker-compose &> /dev/null; then
DOCKER_COMPOSE="docker-compose"
elif docker compose version &> /dev/null; then
DOCKER_COMPOSE="docker compose"
else
echo "❌ 未找到Docker Compose请安装Docker Compose"
exit 1
fi
echo "📦 使用Docker Compose命令: $DOCKER_COMPOSE"
# 停止并删除现有容器
echo "🔄 清理现有容器..."
$DOCKER_COMPOSE down
# 构建并启动服务
echo "🔨 构建并启动服务..."
$DOCKER_COMPOSE up --build -d
# 等待服务启动
echo "⏳ 等待服务启动..."
sleep 10
# 检查服务状态
echo "📊 服务状态:"
$DOCKER_COMPOSE ps
echo ""
echo "✅ 系统启动完成!"
echo "🌐 前端访问地址: http://localhost:3000"
echo "🔧 后端API地址: http://localhost:8080"
echo "🗄️ 数据库地址: localhost:5432"
echo ""
echo "📝 查看日志: $DOCKER_COMPOSE logs -f"
echo "🛑 停止服务: $DOCKER_COMPOSE down"

View File

@@ -15,11 +15,13 @@ func GetSchedulerStatus(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
status := gin.H{
"hot_drama_scheduler_running": scheduler.IsHotDramaSchedulerRunning(),
"ready_resource_scheduler_running": scheduler.IsReadyResourceRunning(),
"auto_transfer_scheduler_running": scheduler.IsAutoTransferRunning(),
}
SuccessResponse(c, status)
@@ -33,6 +35,7 @@ func StartHotDramaScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if scheduler.IsHotDramaSchedulerRunning() {
ErrorResponse(c, "热播剧定时任务已在运行中", http.StatusBadRequest)
@@ -50,6 +53,7 @@ func StopHotDramaScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if !scheduler.IsHotDramaSchedulerRunning() {
ErrorResponse(c, "热播剧定时任务未在运行", http.StatusBadRequest)
@@ -67,6 +71,7 @@ func TriggerHotDramaScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
scheduler.StartHotDramaScheduler() // 直接启动一次
SuccessResponse(c, gin.H{"message": "手动触发热播剧定时任务成功"})
@@ -80,6 +85,7 @@ func FetchHotDramaNames(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
names, err := scheduler.GetHotDramaNames()
if err != nil {
@@ -97,6 +103,7 @@ func StartReadyResourceScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if scheduler.IsReadyResourceRunning() {
ErrorResponse(c, "待处理资源自动处理任务已在运行中", http.StatusBadRequest)
@@ -114,6 +121,7 @@ func StopReadyResourceScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if !scheduler.IsReadyResourceRunning() {
ErrorResponse(c, "待处理资源自动处理任务未在运行", http.StatusBadRequest)
@@ -131,8 +139,60 @@ func TriggerReadyResourceScheduler(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
// 手动触发一次处理
scheduler.ProcessReadyResources()
SuccessResponse(c, gin.H{"message": "手动触发待处理资源自动处理任务成功"})
}
// 启动自动转存定时任务
func StartAutoTransferScheduler(c *gin.Context) {
scheduler := utils.GetGlobalScheduler(
repoManager.HotDramaRepository,
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if scheduler.IsAutoTransferRunning() {
ErrorResponse(c, "自动转存定时任务已在运行中", http.StatusBadRequest)
return
}
scheduler.StartAutoTransferScheduler()
SuccessResponse(c, gin.H{"message": "自动转存定时任务已启动"})
}
// 停止自动转存定时任务
func StopAutoTransferScheduler(c *gin.Context) {
scheduler := utils.GetGlobalScheduler(
repoManager.HotDramaRepository,
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if !scheduler.IsAutoTransferRunning() {
ErrorResponse(c, "自动转存定时任务未在运行", http.StatusBadRequest)
return
}
scheduler.StopAutoTransferScheduler()
SuccessResponse(c, gin.H{"message": "自动转存定时任务已停止"})
}
// 手动触发自动转存定时任务
func TriggerAutoTransferScheduler(c *gin.Context) {
scheduler := utils.GetGlobalScheduler(
repoManager.HotDramaRepository,
repoManager.ReadyResourceRepository,
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
// 手动触发一次处理
scheduler.ProcessAutoTransfer()
SuccessResponse(c, gin.H{"message": "手动触发自动转存定时任务成功"})
}

View File

@@ -163,9 +163,10 @@ func UpdateSystemConfig(c *gin.Context) {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
if scheduler != nil {
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources)
scheduler.UpdateSchedulerStatusWithAutoTransfer(req.AutoFetchHotDramaEnabled, req.AutoProcessReadyResources, req.AutoTransferEnabled)
}
// 返回更新后的配置

16
main.go
View File

@@ -43,9 +43,10 @@ func main() {
repoManager.ResourceRepository,
repoManager.SystemConfigRepository,
repoManager.PanRepository,
repoManager.CksRepository,
)
// 检查系统配置,决定是否启动待处理资源自动处理任务
// 检查系统配置,决定是否启动各种自动任务
systemConfig, err := repoManager.SystemConfigRepository.GetOrCreateDefault()
if err != nil {
utils.Error("获取系统配置失败: %v", err)
@@ -65,6 +66,14 @@ func main() {
} else {
utils.Info("系统配置中自动拉取热播剧功能已禁用,跳过启动定时任务")
}
// 检查是否启动自动转存任务
if systemConfig.AutoTransferEnabled {
scheduler.StartAutoTransferScheduler()
utils.Info("已启动自动转存任务")
} else {
utils.Info("系统配置中自动转存功能已禁用,跳过启动定时任务")
}
}
// 创建Gin实例
@@ -198,6 +207,11 @@ func main() {
api.POST("/scheduler/ready-resource/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartReadyResourceScheduler)
api.POST("/scheduler/ready-resource/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopReadyResourceScheduler)
api.POST("/scheduler/ready-resource/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerReadyResourceScheduler)
// 自动转存管理路由
api.POST("/scheduler/auto-transfer/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartAutoTransferScheduler)
api.POST("/scheduler/auto-transfer/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopAutoTransferScheduler)
api.POST("/scheduler/auto-transfer/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerAutoTransferScheduler)
}
// 静态文件服务

View File

@@ -18,10 +18,10 @@ var (
)
// GetGlobalScheduler 获取全局调度器实例(单例模式)
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository) *GlobalScheduler {
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository, cksRepo repo.CksRepository) *GlobalScheduler {
once.Do(func() {
globalScheduler = &GlobalScheduler{
scheduler: NewScheduler(hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo, panRepo),
scheduler: NewScheduler(hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo, panRepo, cksRepo),
}
})
return globalScheduler
@@ -140,3 +140,90 @@ func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool,
}
}
}
// StartAutoTransferScheduler 启动自动转存定时任务
func (gs *GlobalScheduler) StartAutoTransferScheduler() {
gs.mutex.Lock()
defer gs.mutex.Unlock()
if gs.scheduler.IsAutoTransferRunning() {
Info("自动转存定时任务已在运行中")
return
}
gs.scheduler.StartAutoTransferScheduler()
Info("全局调度器已启动自动转存定时任务")
}
// StopAutoTransferScheduler 停止自动转存定时任务
func (gs *GlobalScheduler) StopAutoTransferScheduler() {
gs.mutex.Lock()
defer gs.mutex.Unlock()
if !gs.scheduler.IsAutoTransferRunning() {
Info("自动转存定时任务未在运行")
return
}
gs.scheduler.StopAutoTransferScheduler()
Info("全局调度器已停止自动转存定时任务")
}
// IsAutoTransferRunning 检查自动转存定时任务是否在运行
func (gs *GlobalScheduler) IsAutoTransferRunning() bool {
gs.mutex.RLock()
defer gs.mutex.RUnlock()
return gs.scheduler.IsAutoTransferRunning()
}
// ProcessAutoTransfer 手动触发自动转存处理
func (gs *GlobalScheduler) ProcessAutoTransfer() {
gs.mutex.Lock()
defer gs.mutex.Unlock()
gs.scheduler.processAutoTransfer()
}
// UpdateSchedulerStatusWithAutoTransfer 根据系统配置更新调度器状态(包含自动转存)
func (gs *GlobalScheduler) UpdateSchedulerStatusWithAutoTransfer(autoFetchHotDramaEnabled bool, autoProcessReadyResources bool, autoTransferEnabled bool) {
gs.mutex.Lock()
defer gs.mutex.Unlock()
// 处理热播剧自动拉取功能
if autoFetchHotDramaEnabled {
if !gs.scheduler.IsRunning() {
Info("系统配置启用自动拉取热播剧,启动定时任务")
gs.scheduler.StartHotDramaScheduler()
}
} else {
if gs.scheduler.IsRunning() {
Info("系统配置禁用自动拉取热播剧,停止定时任务")
gs.scheduler.StopHotDramaScheduler()
}
}
// 处理待处理资源自动处理功能
if autoProcessReadyResources {
if !gs.scheduler.IsReadyResourceRunning() {
Info("系统配置启用自动处理待处理资源,启动定时任务")
gs.scheduler.StartReadyResourceScheduler()
}
} else {
if gs.scheduler.IsReadyResourceRunning() {
Info("系统配置禁用自动处理待处理资源,停止定时任务")
gs.scheduler.StopReadyResourceScheduler()
}
}
// 处理自动转存功能
if autoTransferEnabled {
if !gs.scheduler.IsAutoTransferRunning() {
Info("系统配置启用自动转存,启动定时任务")
gs.scheduler.StartAutoTransferScheduler()
}
} else {
if gs.scheduler.IsAutoTransferRunning() {
Info("系统配置禁用自动转存,停止定时任务")
gs.scheduler.StopAutoTransferScheduler()
}
}
}

View File

@@ -1,6 +1,7 @@
package utils
import (
"fmt"
"strings"
"sync"
"time"
@@ -19,11 +20,14 @@ type Scheduler struct {
resourceRepo repo.ResourceRepository
systemConfigRepo repo.SystemConfigRepository
panRepo repo.PanRepository
cksRepo repo.CksRepository
stopChan chan bool
isRunning bool
readyResourceRunning bool
autoTransferRunning bool
processingMutex sync.Mutex // 防止ready_resource任务重叠执行
hotDramaMutex sync.Mutex // 防止热播剧任务重叠执行
autoTransferMutex sync.Mutex // 防止自动转存任务重叠执行
// 平台映射缓存
panCache map[string]*uint // serviceType -> panID
@@ -31,7 +35,7 @@ type Scheduler struct {
}
// NewScheduler 创建新的定时任务管理器
func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository) *Scheduler {
func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository, panRepo repo.PanRepository, cksRepo repo.CksRepository) *Scheduler {
return &Scheduler{
doubanService: NewDoubanService(),
hotDramaRepo: hotDramaRepo,
@@ -39,11 +43,14 @@ func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.R
resourceRepo: resourceRepo,
systemConfigRepo: systemConfigRepo,
panRepo: panRepo,
cksRepo: cksRepo,
stopChan: make(chan bool),
isRunning: false,
readyResourceRunning: false,
autoTransferRunning: false,
processingMutex: sync.Mutex{},
hotDramaMutex: sync.Mutex{},
autoTransferMutex: sync.Mutex{},
panCache: make(map[string]*uint),
}
}
@@ -571,3 +578,225 @@ func (s *Scheduler) getPanIDByServiceType(serviceType panutils.ServiceType) *uin
func (s *Scheduler) IsReadyResourceRunning() bool {
return s.readyResourceRunning
}
// StartAutoTransferScheduler 启动自动转存定时任务
func (s *Scheduler) StartAutoTransferScheduler() {
if s.autoTransferRunning {
Info("自动转存定时任务已在运行中")
return
}
s.autoTransferRunning = true
Info("启动自动转存定时任务")
go func() {
// 获取系统配置中的间隔时间
config, err := s.systemConfigRepo.GetOrCreateDefault()
interval := 5 * time.Minute // 默认5分钟
if err == nil && config.AutoProcessInterval > 0 {
interval = time.Duration(config.AutoProcessInterval) * time.Minute
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
Info("自动转存定时任务已启动,间隔时间: %v", interval)
// 立即执行一次
s.processAutoTransfer()
for {
select {
case <-ticker.C:
// 使用TryLock防止任务重叠执行
if s.autoTransferMutex.TryLock() {
go func() {
defer s.autoTransferMutex.Unlock()
s.processAutoTransfer()
}()
} else {
Info("上一次自动转存任务还在执行中,跳过本次执行")
}
case <-s.stopChan:
Info("停止自动转存定时任务")
return
}
}
}()
}
// StopAutoTransferScheduler 停止自动转存定时任务
func (s *Scheduler) StopAutoTransferScheduler() {
if !s.autoTransferRunning {
Info("自动转存定时任务未在运行")
return
}
s.stopChan <- true
s.autoTransferRunning = false
Info("已发送停止信号给自动转存定时任务")
}
// IsAutoTransferRunning 检查自动转存定时任务是否在运行
func (s *Scheduler) IsAutoTransferRunning() bool {
return s.autoTransferRunning
}
// processAutoTransfer 处理自动转存
func (s *Scheduler) processAutoTransfer() {
Info("开始处理自动转存...")
// 检查系统配置,确认是否启用自动转存
config, err := s.systemConfigRepo.GetOrCreateDefault()
if err != nil {
Error("获取系统配置失败: %v", err)
return
}
if !config.AutoTransferEnabled {
Info("自动转存功能已禁用")
return
}
// 获取所有有效的网盘账号
accounts, err := s.cksRepo.FindAll()
if err != nil {
Error("获取网盘账号失败: %v", err)
return
}
if len(accounts) == 0 {
Info("没有可用的网盘账号")
return
}
Info("找到 %d 个网盘账号,开始自动转存处理...", len(accounts))
// 获取需要转存的资源
resources, err := s.getResourcesForTransfer(config)
if err != nil {
Error("获取需要转存的资源失败: %v", err)
return
}
if len(resources) == 0 {
Info("没有需要转存的资源")
return
}
Info("找到 %d 个需要转存的资源", len(resources))
// 执行自动转存
transferCount := 0
for _, resource := range resources {
if err := s.transferResource(resource, accounts, config); err != nil {
Error("转存资源失败 (ID: %d): %v", resource.ID, err)
} else {
transferCount++
Info("成功转存资源: %s", resource.Title)
}
}
Info("自动转存处理完成,共转存 %d 个资源", transferCount)
}
// getResourcesForTransfer 获取需要转存的资源
func (s *Scheduler) getResourcesForTransfer(config *entity.SystemConfig) ([]*entity.Resource, error) {
// TODO: 实现获取需要转存的资源逻辑
// 1. 获取所有有效的资源
// 2. 根据配置的转存限制天数过滤资源
// 3. 排除已经转存过的资源
// 4. 按优先级排序(可以根据浏览次数、创建时间等)
Info("获取需要转存的资源 - 限制天数: %d", config.AutoTransferLimitDays)
// 临时返回空数组,等待具体实现
return []*entity.Resource{}, nil
}
// transferResource 转存单个资源
func (s *Scheduler) transferResource(resource *entity.Resource, accounts []entity.Cks, config *entity.SystemConfig) error {
// TODO: 实现单个资源的转存逻辑
// 1. 选择合适的网盘账号根据剩余空间、VIP状态等
// 2. 检查账号剩余空间是否满足最小空间要求
// 3. 调用网盘API进行转存
// 4. 更新资源状态和转存记录
// 5. 更新账号使用空间
Info("开始转存资源: %s (ID: %d)", resource.Title, resource.ID)
// 选择最佳账号
selectedAccount := s.selectBestAccount(accounts, config)
if selectedAccount == nil {
return fmt.Errorf("没有合适的网盘账号")
}
Info("选择账号: %s (剩余空间: %d GB)", selectedAccount.Username, selectedAccount.LeftSpace/1024/1024/1024)
// TODO: 执行实际的转存操作
// 这里需要调用网盘API进行转存
return nil
}
// selectBestAccount 选择最佳网盘账号
func (s *Scheduler) selectBestAccount(accounts []entity.Cks, config *entity.SystemConfig) *entity.Cks {
// TODO: 实现账号选择逻辑
// 1. 过滤出有效的账号
// 2. 检查剩余空间是否满足最小要求
// 3. 优先选择VIP账号
// 4. 优先选择剩余空间大的账号
// 5. 考虑账号的使用频率(避免单个账号过度使用)
minSpaceBytes := int64(config.AutoTransferMinSpace) * 1024 * 1024 * 1024 // 转换为字节
var bestAccount *entity.Cks
var maxScore int64 = -1
for _, account := range accounts {
if !account.IsValid {
continue
}
// 检查剩余空间
if account.LeftSpace < minSpaceBytes {
continue
}
// 计算账号评分
score := s.calculateAccountScore(&account)
if score > maxScore {
maxScore = score
bestAccount = &account
}
}
return bestAccount
}
// calculateAccountScore 计算账号评分
func (s *Scheduler) calculateAccountScore(account *entity.Cks) int64 {
// TODO: 实现账号评分算法
// 1. VIP账号加分
// 2. 剩余空间大的账号加分
// 3. 使用率低的账号加分
// 4. 可以根据历史使用情况调整评分
score := int64(0)
// VIP账号加分
if account.VipStatus {
score += 1000
}
// 剩余空间加分每GB加1分
score += account.LeftSpace / (1024 * 1024 * 1024)
// 使用率加分(使用率越低分数越高)
if account.Space > 0 {
usageRate := float64(account.UsedSpace) / float64(account.Space)
score += int64((1 - usageRate) * 500) // 使用率越低,加分越多
}
return score
}