package handler import ( "net/http" "net/url" "strconv" "strings" "bj_power_workstation/internal/mes" ) // taskCurrentHandler 代理 MES GET /api/internal/station/task?dockCode=。 // 响应体原样透传(MES 已按 {code,message,data} 封装,这里不再二次包装,避免双重嵌套); // MES 不可达时返回 502 JSON 错误。 func taskCurrentHandler(mesc *mes.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { dockCode := strings.TrimSpace(r.URL.Query().Get("dockCode")) if dockCode == "" { fail(w, http.StatusBadRequest, "缺少 dockCode 参数") return } body, _, err := mesc.Get("/api/internal/station/task", url.Values{"dockCode": {dockCode}}) if err != nil { fail(w, http.StatusBadGateway, "MES 服务不可达:"+errString(err)) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) _, _ = w.Write(body) } } // ---------- 工艺 PDF 直通代理 ---------- // // 二选一说明:本工程采用「本地无鉴权前缀 /pdfopen/」方案——iframe/img/window.open // 无法方便地携带 Authorization 头,因此对 /pdfopen/ 路径免 JWT 并解析路径参数作为文件名, // 内部仍走 MES /api/internal/files/(内部 API 由 X-API-TOKEN 保护)。 // 同时保留需登录态的 GET /api/pdf?name= 供程序化调用。 // 两端点均把文件流原样转发并透传 Content-Type/Content-Disposition,不套 {code,message,data}。 // pdfQueryHandler GET /api/pdf?name=(需登录) func pdfQueryHandler(mesc *mes.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { forwardFile(w, mesc, strings.TrimSpace(r.URL.Query().Get("name"))) } } // pdfOpenHandler GET /pdfopen/(免鉴权,供 iframe 直链) func pdfOpenHandler(mesc *mes.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // go-zero 路由参数也可用 PathVar,此处直接解析 URL 以减少对外部 API 的依赖 raw := strings.TrimPrefix(r.URL.EscapedPath(), "/pdfopen/") name, err := url.PathUnescape(raw) if err != nil { fail(w, http.StatusBadRequest, "非法文件名") return } forwardFile(w, mesc, name) } } // forwardFile 校验单段文件名后拉取 MES 文件流并原样转发。 func forwardFile(w http.ResponseWriter, mesc *mes.Client, name string) { name = strings.TrimSpace(name) if name == "" { fail(w, http.StatusBadRequest, "缺少文件名") return } if strings.ContainsAny(name, `/\`) || name == "." || name == ".." || strings.Contains(name, "..") { fail(w, http.StatusBadRequest, "非法文件名") return } body, header, err := mesc.Get("/api/internal/files/"+url.PathEscape(name), nil) if err != nil { fail(w, http.StatusBadGateway, "工艺文件获取失败:"+errString(err)) return } ct := header.Get("Content-Type") if ct == "" { ct = "application/pdf" } w.Header().Set("Content-Type", ct) if cd := header.Get("Content-Disposition"); cd != "" { w.Header().Set("Content-Disposition", cd) } w.Header().Set("X-Original-Filename", strconv.Quote(name)) w.WriteHeader(http.StatusOK) _, _ = w.Write(body) } // errString 压缩错误信息长度便于前端展示。 func errString(err error) string { s := strings.TrimSpace(err.Error()) s = strings.ReplaceAll(s, "\n", " ") const maxLen = 180 if len(s) > maxLen { return s[:maxLen] + "..." } return s }