Files
bj_power/bj_power_workstation/internal/handler/proxy.go
T
SunYF ae4c292487 feat: 完成产线MES系统全流程功能迭代与优化
本提交完成了多个核心模块的功能完善与业务对齐:
1. 工位终端:固定工位配置、移除自选工位逻辑、适配配置化工位号
2. MES核心:重构工位组合逻辑、新增工单/流程状态管理、补全报工/追溯逻辑
3. WMS客户端:新增修改密码功能、优化帮助弹窗逻辑
4. 文档与权限:补充完整操作手册、统一菜单名称与权限描述
5. 修复多业务校验:排产数量校验、工单状态校验、工位绑定规则
2026-08-31 12:58:28 +08:00

153 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/<name>
// 内部仍走 MES /api/internal/files/<name>(由 X-API-TOKEN 保护)。
// 启用本地预缓存后,首次访问回源下载并落盘,后续直接读本地缓存(断网也能查看)。
// 两端点均把文件流原样转发并透传 Content-Type,不套 {code,message,data}。
// pdfQueryHandler GET /api/pdf?name=<file>(需登录)
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/<name>(免鉴权,供 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)
}