c8ee9685b9
批量创建任务使用单事务数据库插入,显著提升性能。 移除任务列表的批量取消/删除进度显示(批处理接口无意义)。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
289 lines
7.0 KiB
Go
289 lines
7.0 KiB
Go
package router
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"time"
|
|
|
|
"bilidown/internal/task"
|
|
"bilidown/internal/util"
|
|
)
|
|
|
|
func createTask(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
if r.Method != http.MethodPost {
|
|
util.Res{Success: false, Message: "不支持的请求方法"}.Write(w)
|
|
return
|
|
}
|
|
var body []task.TaskInDB
|
|
err := json.NewDecoder(r.Body).Decode(&body)
|
|
if err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
util.Res{Success: false, Message: "任务列表为空"}.Write(w)
|
|
return
|
|
}
|
|
|
|
db := util.MustGetDB()
|
|
defer db.Close()
|
|
|
|
// 预先获取下载目录
|
|
folder, err := util.GetCurrentFolder(db)
|
|
if err != nil {
|
|
util.Res{Success: false, Message: fmt.Sprintf("获取下载目录失败: %v", err)}.Write(w)
|
|
return
|
|
}
|
|
|
|
// 批量验证参数
|
|
for _, item := range body {
|
|
if !util.CheckBvidFormat(item.Bvid) {
|
|
util.Res{Success: false, Message: fmt.Sprintf("bvid 格式错误: %s", item.Bvid)}.Write(w)
|
|
return
|
|
}
|
|
if item.Title == "" || item.Owner == "" {
|
|
util.Res{Success: false, Message: "标题或作者为空"}.Write(w)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 批量数据库插入(单事务)
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
util.Res{Success: false, Message: fmt.Sprintf("开启事务失败: %v", err)}.Write(w)
|
|
return
|
|
}
|
|
|
|
stmt, err := tx.Prepare(`INSERT INTO "task" ("bvid", "cid", "format", "title", "owner", "cover", "status", "folder", "duration", "download_type")
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
util.Res{Success: false, Message: fmt.Sprintf("准备语句失败: %v", err)}.Write(w)
|
|
return
|
|
}
|
|
defer stmt.Close()
|
|
|
|
tasks := make([]*task.Task, 0, len(body))
|
|
for _, item := range body {
|
|
item.Folder = folder
|
|
item.Status = "waiting"
|
|
item.Title = util.FilterFileName(item.Title)
|
|
_task := task.Task{TaskInDB: item}
|
|
|
|
util.SqliteLock.Lock()
|
|
result, err := stmt.Exec(
|
|
item.Bvid, item.Cid, item.Format, item.Title, item.Owner,
|
|
item.Cover, item.Status, item.Folder, item.Duration, item.DownloadType,
|
|
)
|
|
util.SqliteLock.Unlock()
|
|
|
|
if err != nil {
|
|
tx.Rollback()
|
|
util.Res{Success: false, Message: fmt.Sprintf("插入任务失败: %v", err)}.Write(w)
|
|
return
|
|
}
|
|
|
|
_task.ID, _ = result.LastInsertId()
|
|
_task.CreateAt = time.Now()
|
|
tasks = append(tasks, &_task)
|
|
}
|
|
|
|
util.SqliteLock.Lock()
|
|
err = tx.Commit()
|
|
util.SqliteLock.Unlock()
|
|
|
|
if err != nil {
|
|
util.Res{Success: false, Message: fmt.Sprintf("提交事务失败: %v", err)}.Write(w)
|
|
return
|
|
}
|
|
|
|
// 批量启动下载任务
|
|
for _, _task := range tasks {
|
|
go _task.Start()
|
|
}
|
|
|
|
util.Res{Success: true, Message: fmt.Sprintf("成功创建 %d 个任务", len(tasks))}.Write(w)
|
|
}
|
|
|
|
func getActiveTask(w http.ResponseWriter, r *http.Request) {
|
|
util.Res{Success: true, Data: task.GlobalTaskList}.Write(w)
|
|
}
|
|
|
|
func getTaskList(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
db := util.MustGetDB()
|
|
defer db.Close()
|
|
page, err := strconv.Atoi(r.FormValue("page"))
|
|
if err != nil {
|
|
page = 0
|
|
}
|
|
pageSize, err := strconv.Atoi(r.FormValue("pageSize"))
|
|
if err != nil {
|
|
pageSize = 360
|
|
}
|
|
tasks, err := task.GetTaskList(db, page, pageSize)
|
|
if err != nil {
|
|
util.Res{Success: false, Message: err.Error()}.Write(w)
|
|
return
|
|
}
|
|
util.Res{Success: true, Message: "获取成功", Data: tasks}.Write(w)
|
|
}
|
|
|
|
// showFile 调用 Explorer 查看文件位置
|
|
func showFile(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
filePath := r.FormValue("filePath")
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
// 根据操作系统选择命令
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
// Windows 使用 explorer
|
|
cmd = exec.Command("explorer", "/select,", filePath)
|
|
case "darwin":
|
|
// macOS 使用 open
|
|
cmd = exec.Command("open", "-R", filePath)
|
|
case "linux":
|
|
// Linux 使用 xdg-open
|
|
cmd = exec.Command("xdg-open", filePath)
|
|
default:
|
|
util.Res{Success: false, Message: "不支持的操作系统"}.Write(w)
|
|
return
|
|
}
|
|
err := cmd.Start()
|
|
if err != nil {
|
|
util.Res{Success: false, Message: err.Error()}.Write(w)
|
|
return
|
|
}
|
|
util.Res{Success: true, Message: "操作成功"}.Write(w)
|
|
}
|
|
|
|
func deleteTask(w http.ResponseWriter, r *http.Request) {
|
|
// 支持单个删除(GET 参数 id)和批量删除(POST 参数 ids)
|
|
var taskIDs []int
|
|
|
|
if r.Method == http.MethodPost {
|
|
defer r.Body.Close()
|
|
var body struct {
|
|
IDs []int `json:"ids"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
taskIDs = body.IDs
|
|
} else {
|
|
taskIDStr := r.FormValue("id")
|
|
taskID, err := strconv.Atoi(taskIDStr)
|
|
if err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
taskIDs = []int{taskID}
|
|
}
|
|
|
|
if len(taskIDs) == 0 {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
|
|
db := util.MustGetDB()
|
|
defer db.Close()
|
|
|
|
// 批量删除结果
|
|
successCount := 0
|
|
failedTasks := []struct {
|
|
ID int
|
|
Error string
|
|
}{}
|
|
|
|
for _, taskID := range taskIDs {
|
|
// 只删除数据库记录,不删除已下载的视频文件
|
|
err := task.DeleteTask(db, taskID)
|
|
if err != nil {
|
|
failedTasks = append(failedTasks, struct {
|
|
ID int
|
|
Error string
|
|
}{ID: taskID, Error: fmt.Sprintf("数据库删除失败: %v", err)})
|
|
continue
|
|
}
|
|
successCount++
|
|
}
|
|
|
|
if len(failedTasks) == 0 {
|
|
util.Res{Success: true, Message: fmt.Sprintf("成功删除 %d 个任务", successCount)}.Write(w)
|
|
} else {
|
|
util.Res{
|
|
Success: successCount > 0,
|
|
Message: fmt.Sprintf("成功删除 %d 个任务,失败 %d 个", successCount, len(failedTasks)),
|
|
Data: failedTasks,
|
|
}.Write(w)
|
|
}
|
|
}
|
|
|
|
func cancelTask(w http.ResponseWriter, r *http.Request) {
|
|
// 支持单个取消(GET 参数 id)和批量取消(POST 参数 ids)
|
|
var taskIDs []int64
|
|
|
|
if r.Method == http.MethodPost {
|
|
defer r.Body.Close()
|
|
var body struct {
|
|
IDs []int64 `json:"ids"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
taskIDs = body.IDs
|
|
} else {
|
|
taskIDStr := r.FormValue("id")
|
|
taskID, err := strconv.ParseInt(taskIDStr, 10, 64)
|
|
if err != nil {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
taskIDs = []int64{taskID}
|
|
}
|
|
|
|
if len(taskIDs) == 0 {
|
|
util.Res{Success: false, Message: "参数错误"}.Write(w)
|
|
return
|
|
}
|
|
|
|
// 批量取消结果
|
|
successCount := 0
|
|
failedIDs := []int64{}
|
|
|
|
for _, taskID := range taskIDs {
|
|
if task.CancelTask(taskID) {
|
|
successCount++
|
|
} else {
|
|
failedIDs = append(failedIDs, taskID)
|
|
}
|
|
}
|
|
|
|
if len(failedIDs) == 0 {
|
|
util.Res{Success: true, Message: fmt.Sprintf("成功取消 %d 个任务", successCount)}.Write(w)
|
|
} else {
|
|
util.Res{
|
|
Success: successCount > 0,
|
|
Message: fmt.Sprintf("成功取消 %d 个任务,失败 %d 个", successCount, len(failedIDs)),
|
|
Data: failedIDs,
|
|
}.Write(w)
|
|
}
|
|
}
|