2e2c161770
- backed/: Go Fiber backend with cpdaily integration - frondend/: Vue 3 + Vite frontend - Dockerfile: multi-stage build (Node → Go → Alpine) - deploy/docker-compose.yml: production compose file - .woodpecker/build.yml: CI/CD with blue-green zero-downtime deploy - /health endpoint for deployment health checks - Port 8884/8885 blue-green swap via 1Panel OpenResty
640 lines
19 KiB
Go
640 lines
19 KiB
Go
package handler
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
"time"
|
|
|
|
"cpdaily-new/internal/database"
|
|
"cpdaily-new/internal/model"
|
|
"cpdaily-new/internal/ratelimit"
|
|
cpd "cpdaily-new/internal/service/cpdaily"
|
|
"cpdaily-new/internal/utils"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GetTaskTitles 并发获取 sign + sleep_check 两种类型的近期任务
|
|
func GetTaskTitles(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var user model.SysUser
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return utils.FailAuth(c, "用户不存在")
|
|
}
|
|
if user.Cookie == "" {
|
|
return utils.Fail(c, 400, "请先登录")
|
|
}
|
|
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
tasks := cpd.FetchAllRecentTasks(cookieMap, host)
|
|
return utils.Success(c, tasks)
|
|
}
|
|
|
|
// CreateTask 创建任务
|
|
type CreateTaskRequest struct {
|
|
TaskType string `json:"taskType"`
|
|
Title string `json:"title"`
|
|
RealTitle string `json:"realTitle"`
|
|
SignWid string `json:"signWid"`
|
|
SignInstanceWid string `json:"signInstanceWid"`
|
|
}
|
|
|
|
func CreateTask(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var req CreateTaskRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return utils.FailParam(c, "请求参数错误")
|
|
}
|
|
if req.TaskType == "" || req.Title == "" {
|
|
return utils.FailParam(c, "参数不完整")
|
|
}
|
|
|
|
task := model.CpTask{
|
|
UserID: userID,
|
|
TaskType: req.TaskType,
|
|
Title: req.Title,
|
|
RealTitle: req.RealTitle,
|
|
SignWid: req.SignWid,
|
|
SignInstanceWid: req.SignInstanceWid,
|
|
TaskStatus: "pending",
|
|
}
|
|
if err := database.DB.Create(&task).Error; err != nil {
|
|
slog.Error("创建任务失败", "error", err)
|
|
return utils.FailServer(c, "创建任务失败")
|
|
}
|
|
|
|
return utils.Success(c, task)
|
|
}
|
|
|
|
// GetTaskForm 获取任务表单详情(从 cpdaily 拉取并解析)
|
|
func GetTaskForm(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
var task model.CpTask
|
|
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&task).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return utils.Fail(c, 404, "任务不存在")
|
|
}
|
|
return utils.FailServer(c, "查询失败")
|
|
}
|
|
|
|
var user model.SysUser
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return utils.FailAuth(c, "用户不存在")
|
|
}
|
|
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
// 从 cpdaily 拉取任务详情
|
|
detail, err := cpd.FetchTaskDetail(cookieMap, host, cpd.TaskType(task.TaskType), task.SignWid, task.SignInstanceWid, task.Title)
|
|
if err != nil {
|
|
// cookie 过期时自动尝试重新登录
|
|
if isCookieExpired(err) {
|
|
slog.Info("获取任务表单cookie过期,尝试刷新", "taskId", id)
|
|
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
|
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
|
detail, err = cpd.FetchTaskDetail(cookieMap, host, cpd.TaskType(task.TaskType), task.SignWid, task.SignInstanceWid, task.Title)
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
slog.Warn("获取任务表单失败", "taskId", id, "error", err)
|
|
if errors.Is(err, ErrPasswordChanged) {
|
|
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
|
}
|
|
return utils.Fail(c, 503, "获取任务表单失败: "+err.Error())
|
|
}
|
|
|
|
// 获取历史数据用于预填充(含月份降级)
|
|
historyRows := cpd.FetchHistoryFormData(cookieMap, host, cpd.TaskType(task.TaskType), detail.RealTitle, task.SignInstanceWid)
|
|
|
|
// 解析为表单字段
|
|
formItems := cpd.ParseTaskForm(detail, historyRows)
|
|
|
|
return utils.Success(c, fiber.Map{
|
|
"formItems": formItems,
|
|
"realTitle": detail.RealTitle,
|
|
"isPhoto": detail.IsPhoto,
|
|
})
|
|
}
|
|
|
|
// SaveTaskForm 保存用户配置的表单数据
|
|
type SaveTaskFormRequest struct {
|
|
FormData []model.CpTaskFormItem `json:"formData"`
|
|
}
|
|
|
|
func SaveTaskForm(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
var req SaveTaskFormRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return utils.FailParam(c, "请求参数错误")
|
|
}
|
|
|
|
var task model.CpTask
|
|
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&task).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return utils.Fail(c, 404, "任务不存在")
|
|
}
|
|
return utils.FailServer(c, "查询失败")
|
|
}
|
|
|
|
// 序列化为 JSON
|
|
formData, _ := json.Marshal(req.FormData)
|
|
database.DB.Model(&task).Update("form_data", string(formData))
|
|
|
|
return utils.Success(c, nil)
|
|
}
|
|
|
|
// TaskWithMatch 任务列表附带匹配状态
|
|
type TaskWithMatch struct {
|
|
model.CpTask
|
|
MatchStatus string `json:"matchStatus"` // matched_unsigned, matched_signed, no_match, check_failed
|
|
}
|
|
|
|
// globMatch 简单通配符匹配(仅支持 *)
|
|
func globMatch(str, pattern string) bool {
|
|
if pattern == "" {
|
|
return str == ""
|
|
}
|
|
parts := strings.Split(pattern, "*")
|
|
if len(parts) == 1 {
|
|
return str == pattern
|
|
}
|
|
// 检查前缀
|
|
if parts[0] != "" && !strings.HasPrefix(str, parts[0]) {
|
|
return false
|
|
}
|
|
str = str[len(parts[0]):]
|
|
// 检查中间部分
|
|
for i := 1; i < len(parts)-1; i++ {
|
|
part := parts[i]
|
|
if part == "" {
|
|
continue
|
|
}
|
|
idx := strings.Index(str, part)
|
|
if idx < 0 {
|
|
return false
|
|
}
|
|
str = str[idx+len(part):]
|
|
}
|
|
// 检查后缀
|
|
last := parts[len(parts)-1]
|
|
if last != "" && !strings.HasSuffix(str, last) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ListTasks 查询用户任务列表(附带今日任务匹配状态)
|
|
func ListTasks(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var tasks []model.CpTask
|
|
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Find(&tasks)
|
|
|
|
// 尝试获取今日任务进行匹配
|
|
var user model.SysUser
|
|
var todayTasks []cpd.TodayTaskInfo
|
|
fetchOk := false
|
|
|
|
if err := database.DB.First(&user, userID).Error; err == nil && user.Cookie != "" {
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
// 并发获取两种任务类型
|
|
type result struct {
|
|
tasks []cpd.TodayTaskInfo
|
|
err error
|
|
}
|
|
ch := make(chan result, 2)
|
|
for _, tt := range []cpd.TaskType{cpd.TaskTypeSign, cpd.TaskTypeSleepCheck} {
|
|
go func(t cpd.TaskType) {
|
|
tasks, err := cpd.FetchTodayTasks(cookieMap, host, t)
|
|
if err == nil {
|
|
for i := range tasks {
|
|
tasks[i].TaskType = string(t)
|
|
}
|
|
}
|
|
ch <- result{tasks, err}
|
|
}(tt)
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
r := <-ch
|
|
if r.err == nil {
|
|
todayTasks = append(todayTasks, r.tasks...)
|
|
fetchOk = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// 构造返回结果
|
|
result := make([]TaskWithMatch, len(tasks))
|
|
for i, task := range tasks {
|
|
item := TaskWithMatch{CpTask: task, MatchStatus: "check_failed"}
|
|
if fetchOk {
|
|
item.MatchStatus = "no_match"
|
|
// 用 title 做 glob 匹配(支持 * 通配符)
|
|
for _, tt := range todayTasks {
|
|
if tt.TaskType != task.TaskType {
|
|
continue
|
|
}
|
|
if globMatch(tt.TaskName, task.Title) {
|
|
if tt.IsSigned {
|
|
item.MatchStatus = "matched_signed"
|
|
} else {
|
|
item.MatchStatus = "matched_unsigned"
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
result[i] = item
|
|
}
|
|
|
|
return utils.Success(c, result)
|
|
}
|
|
|
|
// DeleteTask 删除任务
|
|
func DeleteTask(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
result := database.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&model.CpTask{})
|
|
if result.RowsAffected == 0 {
|
|
return utils.Fail(c, 404, "任务不存在")
|
|
}
|
|
|
|
return utils.Success(c, nil)
|
|
}
|
|
|
|
// ExecuteTask 同步执行签到(SSE 流式返回状态)
|
|
func ExecuteTask(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
var task model.CpTask
|
|
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&task).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return utils.Fail(c, 404, "任务不存在")
|
|
}
|
|
return utils.FailServer(c, "查询失败")
|
|
}
|
|
|
|
if task.TaskStatus == "executing" {
|
|
return utils.Fail(c, 400, "任务正在执行中")
|
|
}
|
|
if task.FormData == "" {
|
|
return utils.Fail(c, 400, "请先配置表单数据")
|
|
}
|
|
|
|
var user model.SysUser
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return utils.FailAuth(c, "用户不存在")
|
|
}
|
|
|
|
// 设置 SSE 头
|
|
c.Set("Content-Type", "text/event-stream")
|
|
c.Set("Cache-Control", "no-cache")
|
|
c.Set("Connection", "keep-alive")
|
|
c.Set("X-Accel-Buffering", "no")
|
|
|
|
// SSE 写入器:所有检查在流内进行,通过 SSE event 返回结果
|
|
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
|
|
sendEvent := func(event string, data map[string]interface{}) {
|
|
d, _ := json.Marshal(data)
|
|
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event, string(d))
|
|
w.Flush()
|
|
}
|
|
|
|
// 保存执行日志
|
|
saveLog := func(isSuccess bool, errMsg, realTitle string) {
|
|
logEntry := model.CpTaskLog{
|
|
CpTaskID: task.ID,
|
|
UserID: userID,
|
|
TaskType: task.TaskType,
|
|
Title: task.Title,
|
|
RealTitle: realTitle,
|
|
IsSuccess: isSuccess,
|
|
ErrorMessage: errMsg,
|
|
}
|
|
database.DB.Create(&logEntry)
|
|
}
|
|
|
|
// 检查可用次数
|
|
if user.SignCount <= 0 {
|
|
slog.Warn("签到次数不足,拒绝执行", "userId", userID, "taskId", id)
|
|
sendEvent("task_error", map[string]interface{}{"message": "可用次数不足,请购买CDK或通过邀请获得次数"})
|
|
return
|
|
}
|
|
|
|
// 限流:同一用户同一任务
|
|
rateKey := fmt.Sprintf("task:%d:%d", userID, task.ID)
|
|
rules := []ratelimit.Rule{
|
|
{Limit: 3, Window: 2 * time.Minute, Label: "2m"},
|
|
{Limit: 6, Window: 1 * time.Hour, Label: "1h"},
|
|
{Limit: 10, Window: 24 * time.Hour, Label: "1d"},
|
|
}
|
|
if ok, msg := ratelimit.AllowMulti(rateKey, rules); !ok {
|
|
slog.Warn("触发限流", "userId", userID, "taskId", id, "msg", msg)
|
|
sendEvent("task_error", map[string]interface{}{"message": msg, "rateLimit": true})
|
|
return
|
|
}
|
|
|
|
// 所有前置检查通过,更新状态为执行中
|
|
database.DB.Model(&task).Updates(map[string]interface{}{
|
|
"task_status": "executing",
|
|
"error_message": "",
|
|
})
|
|
|
|
// 1. 连接成功
|
|
sendEvent("connecting", map[string]interface{}{"status": "connecting", "message": "正在连接今日校园..."})
|
|
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
// 2. 获取表单详情
|
|
sendEvent("fetching_form", map[string]interface{}{"status": "fetching_form", "message": "正在获取签到表单..."})
|
|
detail, err := cpd.FetchTaskDetail(cookieMap, host, cpd.TaskType(task.TaskType), task.SignWid, task.SignInstanceWid, task.Title)
|
|
if err != nil && isCookieExpired(err) {
|
|
slog.Info("获取签到表单cookie过期,尝试自动刷新", "taskId", id)
|
|
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
|
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
|
detail, err = cpd.FetchTaskDetail(cookieMap, host, cpd.TaskType(task.TaskType), task.SignWid, task.SignInstanceWid, task.Title)
|
|
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
|
err = ErrPasswordChanged
|
|
}
|
|
}
|
|
if err != nil {
|
|
errMsg := "获取签到表单失败: " + err.Error()
|
|
if errors.Is(err, ErrPasswordChanged) {
|
|
errMsg = "登录凭证已过期,请重新登录"
|
|
}
|
|
slog.Error("获取表单失败", "taskId", id, "error", err)
|
|
database.DB.Model(&task).Updates(map[string]interface{}{
|
|
"task_status": "fail",
|
|
"error_message": errMsg,
|
|
})
|
|
saveLog(false, errMsg, taskTitle(task))
|
|
sendEvent("task_error", map[string]interface{}{"status": "error", "message": errMsg, "needReLogin": errors.Is(err, ErrPasswordChanged)})
|
|
return
|
|
}
|
|
|
|
// 尝试获取实际标题(用于错误消息和日志中的真实签到/查寝名称)
|
|
if task.RealTitle == "" {
|
|
todayTasks, _ := cpd.FetchTodayTasks(cookieMap, host, cpd.TaskType(task.TaskType))
|
|
for _, t := range todayTasks {
|
|
if t.SignInstanceWid == task.SignInstanceWid && t.TaskName != "" {
|
|
task.RealTitle = t.TaskName
|
|
database.DB.Model(&task).Update("real_title", task.RealTitle)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. 反序列化用户配置的表单
|
|
var formItems []model.CpTaskFormItem
|
|
if err := json.Unmarshal([]byte(task.FormData), &formItems); err != nil {
|
|
errMsg := "表单数据解析失败"
|
|
database.DB.Model(&task).Updates(map[string]interface{}{
|
|
"task_status": "fail",
|
|
"error_message": errMsg,
|
|
})
|
|
saveLog(false, errMsg, taskTitle(task))
|
|
sendEvent("task_error", map[string]interface{}{"status": "error", "message": errMsg})
|
|
return
|
|
}
|
|
|
|
// 4. 填充表单并提交
|
|
sendEvent("submitting", map[string]interface{}{"status": "submitting", "message": "正在提交签到..."})
|
|
|
|
submitForm := cpd.FillSubmitForm(detail.Rows, formItems, user.MobileCode)
|
|
err = cpd.SubmitSign(cookieMap, host, cpd.TaskType(task.TaskType), submitForm, user.MobileCode, user.StudentID)
|
|
if err != nil && isCookieExpired(err) {
|
|
slog.Info("提交签到cookie过期,尝试自动刷新", "taskId", id)
|
|
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
|
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
|
err = cpd.SubmitSign(cookieMap, host, cpd.TaskType(task.TaskType), submitForm, user.MobileCode, user.StudentID)
|
|
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
|
err = ErrPasswordChanged
|
|
}
|
|
}
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("签到失败(%s): %s", taskTitle(task), err.Error())
|
|
if errors.Is(err, ErrPasswordChanged) {
|
|
errMsg = "登录凭证已过期,请重新登录"
|
|
}
|
|
slog.Error("签到失败", "taskId", id, "error", err)
|
|
database.DB.Model(&task).Updates(map[string]interface{}{
|
|
"task_status": "fail",
|
|
"error_message": errMsg,
|
|
})
|
|
saveLog(false, errMsg, taskTitle(task))
|
|
sendEvent("task_error", map[string]interface{}{"status": "error", "message": errMsg, "needReLogin": errors.Is(err, ErrPasswordChanged)})
|
|
return
|
|
}
|
|
|
|
// 5. 成功
|
|
database.DB.Model(&task).Updates(map[string]interface{}{
|
|
"task_status": "success",
|
|
})
|
|
saveLog(true, "", taskTitle(task))
|
|
|
|
// 扣减可用次数(原子操作,防止并发超卖)
|
|
database.DB.Model(&model.SysUser{}).Where("id = ? AND sign_count > 0", userID).
|
|
Update("sign_count", gorm.Expr("sign_count - 1"))
|
|
sendEvent("success", map[string]interface{}{
|
|
"status": "success",
|
|
"message": "签到成功!",
|
|
})
|
|
|
|
slog.Info("签到成功", "taskId", id, "userId", userID, "title", task.Title)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// taskTitle 获取任务的实际显示标题(优先使用 cpdaily 返回的真实标题)
|
|
func taskTitle(task model.CpTask) string {
|
|
if task.RealTitle != "" {
|
|
return task.RealTitle
|
|
}
|
|
return task.Title
|
|
}
|
|
|
|
// ListTaskLogs 获取任务执行日志列表
|
|
func ListTaskLogs(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var logs []model.CpTaskLog
|
|
database.DB.Where("user_id = ?", userID).
|
|
Order("created_at DESC").
|
|
Limit(10).
|
|
Find(&logs)
|
|
|
|
return utils.Success(c, logs)
|
|
}
|
|
|
|
// DeleteTaskLog 删除任务执行日志
|
|
func DeleteTaskLog(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
var log model.CpTaskLog
|
|
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&log).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return utils.Fail(c, 404, "日志不存在")
|
|
}
|
|
return utils.FailServer(c, "查询失败")
|
|
}
|
|
|
|
database.DB.Delete(&log)
|
|
return utils.Success(c, nil)
|
|
}
|
|
|
|
// 确保 task 包导入
|
|
var _ = time.Now
|
|
|
|
// UpdateTaskRequest 更新任务请求
|
|
type UpdateTaskRequest struct {
|
|
Title *string `json:"title"`
|
|
FormData *[]model.CpTaskFormItem `json:"formData"`
|
|
}
|
|
|
|
// UpdateTask 更新任务(标题、表单数据)
|
|
func UpdateTask(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
id := c.Params("id")
|
|
|
|
var req UpdateTaskRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return utils.FailParam(c, "请求参数错误")
|
|
}
|
|
|
|
var task model.CpTask
|
|
if err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&task).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return utils.Fail(c, 404, "任务不存在")
|
|
}
|
|
return utils.FailServer(c, "查询失败")
|
|
}
|
|
|
|
updates := map[string]interface{}{}
|
|
if req.Title != nil {
|
|
updates["title"] = *req.Title
|
|
}
|
|
if req.FormData != nil {
|
|
formData, _ := json.Marshal(req.FormData)
|
|
updates["form_data"] = string(formData)
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
database.DB.Model(&task).Updates(updates)
|
|
}
|
|
|
|
// 重新查询返回最新数据
|
|
database.DB.First(&task, task.ID)
|
|
return utils.Success(c, task)
|
|
}
|
|
|
|
// GetUploadPolicy 获取图片上传策略(前端直接上传到今日校园 OBS)
|
|
func GetUploadPolicy(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var user model.SysUser
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return utils.FailAuth(c, "用户不存在")
|
|
}
|
|
if user.Cookie == "" {
|
|
return utils.Fail(c, 400, "请先登录")
|
|
}
|
|
|
|
var req struct {
|
|
TaskType string `json:"taskType"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil || req.TaskType == "" {
|
|
return utils.FailParam(c, "参数不完整")
|
|
}
|
|
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
policy, err := cpd.GetUploadPolicy(cookieMap, host, cpd.TaskType(req.TaskType))
|
|
if err != nil && isCookieExpired(err) {
|
|
slog.Info("获取上传策略cookie过期,尝试刷新", "userId", userID)
|
|
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
|
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
|
policy, err = cpd.GetUploadPolicy(cookieMap, host, cpd.TaskType(req.TaskType))
|
|
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
|
err = ErrPasswordChanged
|
|
}
|
|
}
|
|
if err != nil {
|
|
slog.Error("获取上传策略失败", "error", err)
|
|
if errors.Is(err, ErrPasswordChanged) {
|
|
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
|
}
|
|
return utils.Fail(c, 503, "获取上传策略失败: "+err.Error())
|
|
}
|
|
|
|
return utils.Success(c, policy)
|
|
}
|
|
|
|
// GetPreviewUrl 获取图片预览 URL
|
|
func GetPreviewUrl(c *fiber.Ctx) error {
|
|
userID := c.Locals("userId").(uint)
|
|
|
|
var user model.SysUser
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return utils.FailAuth(c, "用户不存在")
|
|
}
|
|
if user.Cookie == "" {
|
|
return utils.Fail(c, 400, "请先登录")
|
|
}
|
|
|
|
var req struct {
|
|
TaskType string `json:"taskType"`
|
|
FileKey string `json:"fileKey"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil || req.TaskType == "" || req.FileKey == "" {
|
|
return utils.FailParam(c, "参数不完整")
|
|
}
|
|
|
|
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
|
host := fmt.Sprintf("https://%s.campusphere.net", user.SchoolID)
|
|
|
|
previewUrl, err := cpd.GetPreviewUrl(cookieMap, host, cpd.TaskType(req.TaskType), req.FileKey)
|
|
if err != nil && isCookieExpired(err) {
|
|
slog.Info("获取预览URL cookie过期,尝试刷新", "userId", userID)
|
|
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
|
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
|
previewUrl, err = cpd.GetPreviewUrl(cookieMap, host, cpd.TaskType(req.TaskType), req.FileKey)
|
|
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
|
err = ErrPasswordChanged
|
|
}
|
|
}
|
|
if err != nil {
|
|
slog.Error("获取预览 URL 失败", "error", err)
|
|
if errors.Is(err, ErrPasswordChanged) {
|
|
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
|
}
|
|
return utils.Fail(c, 503, "获取预览 URL 失败: "+err.Error())
|
|
}
|
|
|
|
return utils.Success(c, fiber.Map{
|
|
"previewUrl": previewUrl,
|
|
})
|
|
}
|