mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: schema
This commit is contained in:
@@ -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 账户
|
||||
|
||||
|
||||
30
Dockerfile
30
Dockerfile
@@ -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
|
||||
|
||||
|
||||
66
README.md
66
README.md
@@ -5,10 +5,10 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**一个现代化的网盘资源管理系统,支持多平台网盘资源统一管理和分享**
|
||||
**一个现代化的网盘资源管理系统,支持多网盘自动化转存分享**
|
||||
|
||||
🌐 [在线演示](#) | 📖 [文档](#) | 🐛 问题反馈](#) | ⭐ [给个星标](#)
|
||||
|
||||
@@ -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
198
dock
@@ -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. 大量数据时需要考虑分页优化
|
||||
@@ -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
46
docker-start.sh
Executable 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"
|
||||
@@ -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": "手动触发自动转存定时任务成功"})
|
||||
}
|
||||
|
||||
@@ -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
16
main.go
@@ -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)
|
||||
}
|
||||
|
||||
// 静态文件服务
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user