- MES(B): 工单/BOM/备料/工序字典/PLC下发/拧紧/扫码报工/半成品/AGV/追溯逻辑,WMS与海康RCS客户端,看板Redis缓存API(概览/设备/进度/报警/趋势)+SSE - WMS(C): JWT滑动续签、Excel导入、盘点、内部API、种子数据、独立Postgres配置 - WMS客户端(E): Go网关8891反向代理+内嵌Vue3十页 - 工位终端(D): SQLite本地缓存+模拟拧紧源+内嵌Vue3页面 - Dashboard(A): 看板数据改接MES内部缓存API,SSE实时刷新+vite代理 - 清理各项目球形磨遗留代码,新增部署手册.md
104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
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/<name>」方案——iframe/img/window.open
|
|
// 无法方便地携带 Authorization 头,因此对 /pdfopen/ 路径免 JWT 并解析路径参数作为文件名,
|
|
// 内部仍走 MES /api/internal/files/<name>(内部 API 由 X-API-TOKEN 保护)。
|
|
// 同时保留需登录态的 GET /api/pdf?name=<file> 供程序化调用。
|
|
// 两端点均把文件流原样转发并透传 Content-Type/Content-Disposition,不套 {code,message,data}。
|
|
|
|
// pdfQueryHandler GET /api/pdf?name=<file>(需登录)
|
|
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/<name>(免鉴权,供 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
|
|
}
|