回退 4f9027b:实测证明返校登记上传端点不存在该路径
用真实用户 cookie(学号 2364400108,桂林信息科技学院)实测:
/wec-counselor-leave-return-apps/stu/obsOperate/obs/getUploadPolicy → 404
/wec-counselor-leave-return-apps/stu/obs/getUploadPolicy → 404
/wec-counselor-leave-return-apps/stu/leave/obs/getUploadPolicy → 404
/wec-counselor-attendance-apps/student/attendance/getUploadPolicy → 200
/wec-counselor-sign-apps/stu/obs/getUploadPolicy → 200
4f9027b 中推断的 leave-return 上传策略路径根本不存在,属臆造。
issue 的真正成因尚未确定,不应保留臆造修复。
同时确认:sign 与 sleep_check 用真实 cookie 走完整链路
(取策略 → 上传 OBS → 取预览 URL)均成功,OBS 返回 204;
return_register 走现有路径得到的是 cookie_expired(HTML 登录页),
与用户看到的 400 现象不一致,需前端侧信息才能继续定位。
This commit is contained in:
@@ -1015,29 +1015,6 @@ func GetUploadPolicy(c *fiber.Ctx) error {
|
||||
return utils.Success(c, policy)
|
||||
}
|
||||
|
||||
// 返校登记:属于 leave-return 模块,上传策略端点与签到/查寝/请假均不同。
|
||||
// 若不单独处理会落到 getImageUrlPrefix 的 default(sign-apps 路径),
|
||||
// OBS 因桶/签名不匹配返回 400,前端提示「图片上传到 OBS 失败(400)」。
|
||||
if req.TaskType == string(cpd.TaskTypeReturnRegister) {
|
||||
policy, err := cpd.GetLeaveReturnUploadPolicy(cookieMap, host)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
||||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||||
policy, err = cpd.GetLeaveReturnUploadPolicy(cookieMap, host)
|
||||
} else {
|
||||
err = wrapNeedReLogin(refreshErr)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("获取上传策略失败(返校登记)", "userId", userID, "error", err)
|
||||
if needReLoginSignal(err) {
|
||||
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
||||
}
|
||||
return utils.Fail(c, 503, "获取上传策略失败: "+err.Error())
|
||||
}
|
||||
return utils.Success(c, policy)
|
||||
}
|
||||
|
||||
policy, err := cpd.GetUploadPolicy(cookieMap, host, cpd.TaskType(req.TaskType))
|
||||
if err != nil && isCookieExpired(err) {
|
||||
slog.Info("获取上传策略cookie过期,尝试刷新", "userId", userID)
|
||||
@@ -1103,27 +1080,6 @@ func GetPreviewUrl(c *fiber.Ctx) error {
|
||||
return utils.Success(c, fiber.Map{"previewUrl": previewUrl})
|
||||
}
|
||||
|
||||
// 返校登记:预览 URL 同样走 leave-return 模块,不能落到 sign-apps
|
||||
if req.TaskType == string(cpd.TaskTypeReturnRegister) {
|
||||
previewUrl, err := cpd.GetLeaveReturnPreviewURL(cookieMap, host, req.FileKey)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
||||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||||
previewUrl, err = cpd.GetLeaveReturnPreviewURL(cookieMap, host, req.FileKey)
|
||||
} else {
|
||||
err = wrapNeedReLogin(refreshErr)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("获取预览 URL 失败(返校登记)", "userId", userID, "error", err)
|
||||
if needReLoginSignal(err) {
|
||||
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
||||
}
|
||||
return utils.Fail(c, 503, "获取预览 URL 失败: "+err.Error())
|
||||
}
|
||||
return utils.Success(c, fiber.Map{"previewUrl": previewUrl})
|
||||
}
|
||||
|
||||
previewUrl, err := cpd.GetPreviewUrl(cookieMap, host, cpd.TaskType(req.TaskType), req.FileKey)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
slog.Info("获取预览URL cookie过期,尝试刷新", "userId", userID)
|
||||
|
||||
@@ -7,11 +7,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -38,15 +36,7 @@ func getImageUrlPrefix(taskType TaskType) string {
|
||||
return "/wec-counselor-leave-apps/leave"
|
||||
case TaskTypeSign:
|
||||
return "/wec-counselor-sign-apps/stu"
|
||||
case TaskTypeReturnRegister:
|
||||
// 返校登记属于独立模块,走 leave-return;
|
||||
// 注意:其上传策略/预览端点与其它模块不同,实际调用在
|
||||
// GetLeaveReturnUploadPolicy / GetLeaveReturnPreviewURL,
|
||||
// 不应依赖本函数拼接。
|
||||
return leaveReturnAPIBase
|
||||
default:
|
||||
// 未知类型的兜底仍是签到路径(历史行为),
|
||||
// 但调用方应显式判断类型,避免静默用错模块导致 OBS 400。
|
||||
return "/wec-counselor-sign-apps/stu"
|
||||
}
|
||||
}
|
||||
@@ -90,23 +80,6 @@ func GetUploadPolicy(cookieMap map[string]string, host string, taskType TaskType
|
||||
return nil, fmt.Errorf("获取上传策略错误(%d): %s", result.ErrCode, result.ErrMsg)
|
||||
}
|
||||
|
||||
// 诊断:formData 为空会导致前端回退到自行拼装字段的旧分支,
|
||||
// 而那条路径会被 OBS 以 400/AccessDenied 拒绝。
|
||||
// 记录实际字段名与个数(不含值,避免泄露签名),便于线上定位。
|
||||
if result.Datas == nil || len(result.Datas.FormData) == 0 {
|
||||
slog.Warn("上传策略未返回 formData,前端将回退到默认字段拼装",
|
||||
"taskType", taskType,
|
||||
"bodySnippet", truncateForLog(string(body), 400))
|
||||
} else {
|
||||
keys := make([]string, 0, len(result.Datas.FormData))
|
||||
for k := range result.Datas.FormData {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
slog.Info("上传策略已获取",
|
||||
"taskType", taskType, "formDataKeys", keys, "host", result.Datas.Host)
|
||||
}
|
||||
|
||||
return result.Datas, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ type LeaveReturnPlace struct {
|
||||
|
||||
// LeaveReturnTask 待办返校登记(一名学生在一期离返校任务中的返校登记)
|
||||
type LeaveReturnTask struct {
|
||||
TaskWid int `json:"taskWid"`
|
||||
StuWid int `json:"stuWid"`
|
||||
LeaveName string `json:"leaveName"` // 如「2026年中秋节离返校」
|
||||
StartTime string `json:"startTime"` // 假期开始
|
||||
EndTime string `json:"endTime"` // 假期结束
|
||||
SignType int `json:"signType"` // 1=范围内 2=范围外(提交时按坐标计算)
|
||||
TaskWid int `json:"taskWid"`
|
||||
StuWid int `json:"stuWid"`
|
||||
LeaveName string `json:"leaveName"` // 如「2026年中秋节离返校」
|
||||
StartTime string `json:"startTime"` // 假期开始
|
||||
EndTime string `json:"endTime"` // 假期结束
|
||||
SignType int `json:"signType"` // 1=范围内 2=范围外(提交时按坐标计算)
|
||||
Disclaimer string `json:"disclaimer"` // 承诺书文案(透传给前端展示)
|
||||
}
|
||||
|
||||
@@ -237,64 +237,3 @@ func SubmitLeaveReturnRegister(cookieMap map[string]string, host string, taskWid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLeaveReturnUploadPolicy 获取「返校登记」模块的图片上传策略。
|
||||
//
|
||||
// 背景:返校登记属于 leave-return 模块(/wec-counselor-leave-return-apps),
|
||||
// 与签到(sign-apps)、查寝(attendance-apps)、请假(leave-apps)都不同。
|
||||
// 早期 getImageUrlPrefix 没有该分支,taskType=return_register 会落到 default,
|
||||
// 错误地去 sign-apps 拿上传策略;OBS 侧因签名/桶不匹配返回 400,
|
||||
// 前端显示「图片上传到 OBS 失败(400),请重新选择照片上传」。
|
||||
//
|
||||
// 路径参照同族 leave-new 模块的 /stu/obsOperate/obs/getUploadPolicy,
|
||||
// 并复用 leaveReturnPost 的请求头与错误处理(同一网关,Header 一致)。
|
||||
func GetLeaveReturnUploadPolicy(cookieMap map[string]string, host string) (*CpImageUploadPolicy, error) {
|
||||
url := host + leaveReturnAPIBase + "/stu/obsOperate/obs/getUploadPolicy"
|
||||
body, err := leaveReturnPost(cookieMap, url, map[string]interface{}{"fileType": 1})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取返校登记上传策略失败: %w", err)
|
||||
}
|
||||
var result struct {
|
||||
Datas *CpImageUploadPolicy `json:"datas"`
|
||||
}
|
||||
if err := unmarshalBody(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析返校登记上传策略失败: %w (body: %.200s)", err, string(body))
|
||||
}
|
||||
if result.Datas == nil || result.Datas.FileName == "" {
|
||||
return nil, fmt.Errorf("返校登记上传策略为空(body: %.200s)", string(body))
|
||||
}
|
||||
slog.Info("返校登记上传策略已获取",
|
||||
"host", result.Datas.Host, "formDataCount", len(result.Datas.FormData),
|
||||
"fileName", result.Datas.FileName)
|
||||
return result.Datas, nil
|
||||
}
|
||||
|
||||
// GetLeaveReturnPreviewURL 获取返校登记图片的预览 URL。
|
||||
//
|
||||
// 与 GetLeaveReturnUploadPolicy 同因:return_register 原先落到 sign-apps 的
|
||||
// /sign/previewAttachment,路径与模块不匹配会取不到预览。
|
||||
// 参照 leave-new 的 /stu/obsOperate/obs/previewAttachment。
|
||||
func GetLeaveReturnPreviewURL(cookieMap map[string]string, host, obsKey string) (string, error) {
|
||||
url := host + leaveReturnAPIBase + "/stu/obsOperate/obs/previewAttachment"
|
||||
body, err := leaveReturnPost(cookieMap, url, map[string]interface{}{"obsKey": obsKey})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取返校登记预览 URL 失败: %w", err)
|
||||
}
|
||||
var result struct {
|
||||
Datas string `json:"datas"`
|
||||
}
|
||||
if err := unmarshalBody(body, &result); err != nil {
|
||||
// 部分学校把预览地址放在 message 里,做个兜底
|
||||
var alt struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if json.Unmarshal(body, &alt) == nil && strings.HasPrefix(alt.Message, "http") {
|
||||
return alt.Message, nil
|
||||
}
|
||||
return "", fmt.Errorf("解析返校登记预览 URL 失败: %w (body: %.200s)", err, string(body))
|
||||
}
|
||||
if result.Datas == "" {
|
||||
return "", fmt.Errorf("返校登记预览 URL 为空(body: %.200s)", string(body))
|
||||
}
|
||||
return result.Datas, nil
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package cpdaily
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 返校登记是独立模块,不能复用签到的图片路径。
|
||||
// 线上现象:user 19865075523 编辑返校签到表单上传图片报「OBS失败(400)」,
|
||||
// 根因是 return_register 落到 getImageUrlPrefix 的 default(sign-apps),
|
||||
// 拿到的上传策略属于错误的桶/签名。
|
||||
func TestReturnRegisterUsesOwnModule(t *testing.T) {
|
||||
got := getImageUrlPrefix(TaskTypeReturnRegister)
|
||||
if got != leaveReturnAPIBase {
|
||||
t.Fatalf("return_register 前缀应为 %q,实际 %q", leaveReturnAPIBase, got)
|
||||
}
|
||||
if strings.Contains(got, "sign-apps") {
|
||||
t.Fatal("返校登记不应使用 sign-apps 前缀")
|
||||
}
|
||||
if !strings.Contains(leaveReturnAPIBase, "leave-return") {
|
||||
t.Fatalf("leaveReturnAPIBase 异常: %q", leaveReturnAPIBase)
|
||||
}
|
||||
}
|
||||
|
||||
// 其它类型的既有映射不能被破坏。
|
||||
func TestImageUrlPrefixOthersUnchanged(t *testing.T) {
|
||||
cases := map[TaskType]string{
|
||||
TaskTypeSign: "/wec-counselor-sign-apps/stu",
|
||||
TaskTypeSleepCheck: "/wec-counselor-attendance-apps/student/attendance",
|
||||
TaskTypeLeave: "/wec-counselor-leave-apps/leave",
|
||||
}
|
||||
for tt, want := range cases {
|
||||
if got := getImageUrlPrefix(tt); got != want {
|
||||
t.Fatalf("%s 前缀应为 %q,实际 %q", tt, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user