package handler import ( "io" "net/http" "net/url" "os" "strconv" "strings" "bj_power_workstation/internal/mes" "bj_power_workstation/internal/pdfcache" "bj_power_workstation/internal/svc" ) // taskCurrentHandler 代理 MES GET /api/internal/station/task?stationNo=。 // 响应体原样透传(MES 已按 {code,message,data} 封装,不再二次包装)。 func taskCurrentHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { stationNo := atoi(strings.TrimSpace(r.URL.Query().Get("stationNo")), 0) if stationNo <= 0 { fail(w, http.StatusBadRequest, "缺少 stationNo 参数") return } body, _, err := ctx.Mes.Get("/api/internal/station/task", url.Values{"stationNo": {strconv.Itoa(stationNo)}}) 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) } } // workloadHandler 代理 MES GET /api/internal/station/workload?operator=&stationNo=&from=&to=(块6)。 // 工位终端「我的工作量」查询当前登录人全部工位的工作,stationNo 为空=全部工位;MES 响应体原样透传。 func workloadHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { operator := strings.TrimSpace(r.URL.Query().Get("operator")) stationNo := strings.TrimSpace(r.URL.Query().Get("stationNo")) from := strings.TrimSpace(r.URL.Query().Get("from")) to := strings.TrimSpace(r.URL.Query().Get("to")) params := url.Values{} if operator != "" { params.Set("operator", operator) } if stationNo != "" { params.Set("stationNo", stationNo) } if from != "" { params.Set("from", from) } if to != "" { params.Set("to", to) } body, _, err := ctx.Mes.Get("/api/internal/station/workload", params) 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 直通代理(含本地预缓存) ---------- // // 前端 iframe/window.open 无法携带 Authorization,故提供免 JWT 前缀 /pdfopen/, // 内部仍走 MES /api/internal/files/(由 X-API-TOKEN 保护)。 // 启用本地预缓存后,首次访问回源下载并落盘,后续直接读本地缓存(断网也能查看)。 // 两端点均把文件流原样转发并透传 Content-Type,不套 {code,message,data}。 // pdfQueryHandler GET /api/pdf?name=(需登录) func pdfQueryHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { forwardFile(w, ctx.Mes, ctx.PdfCache, strings.TrimSpace(r.URL.Query().Get("name"))) } } // pdfOpenHandler GET /pdfopen/(免鉴权,供 iframe 直链) func pdfOpenHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { raw := strings.TrimPrefix(r.URL.EscapedPath(), "/pdfopen/") name, err := url.PathUnescape(raw) if err != nil { fail(w, http.StatusBadRequest, "非法文件名") return } forwardFile(w, ctx.Mes, ctx.PdfCache, name) } } // forwardFile 校验单段文件名后拉取 MES 文件流并原样转发;优先命中本地预缓存。 func forwardFile(w http.ResponseWriter, mesc *mes.Client, cache *pdfcache.Cache, 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 } // 1) 命中本地预缓存 → 直接回源本地文件 if p, ok := cache.Get(name); ok { f, err := os.Open(p) if err == nil { stream(f, w, name, "") return } f.Close() } // 2) 回源 MES 下载 body, header, err := mesc.Get("/api/internal/files/"+url.PathEscape(name), nil) if err != nil { fail(w, http.StatusBadGateway, "工艺文件获取失败:"+errString(err)) return } // 3) 写入本地预缓存(失败不影响本次响应) _, _ = cache.Put(name, body) streamBytes(body, w, name, header.Get("Content-Type")) } func stream(f io.ReadCloser, w http.ResponseWriter, name, contentType string) { defer f.Close() ct := contentType if ct == "" { ct = "application/pdf" } w.Header().Set("Content-Type", ct) w.Header().Set("X-Original-Filename", strconv.Quote(name)) w.Header().Set("X-PDF-Cache", "hit") w.WriteHeader(http.StatusOK) _, _ = io.Copy(w, f) } func streamBytes(body []byte, w http.ResponseWriter, name, contentType string) { ct := contentType if ct == "" { ct = "application/pdf" } w.Header().Set("Content-Type", ct) w.Header().Set("X-Original-Filename", strconv.Quote(name)) w.Header().Set("X-PDF-Cache", "miss") w.WriteHeader(http.StatusOK) _, _ = w.Write(body) }