Files
bj_power/bj_power_workstation/internal/handler/proxy.go
T
SunYF 0ab21b1234 feat&refactor: 完成多模块功能迭代与配置优化
本次提交覆盖多个业务模块的功能完善与体验优化:
1.  **鉴权与配置调整**:
    - 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
    - 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
    - 工位配置放开1~12限制,改为仅校验大于0
2.  **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3.  **功能新增**:
    - 新增工位类型、工艺路线与产线点位台账模块
    - 添加工艺PDF预览面板、工位终端代理转发接口
    - 新增操作日志按操作人列表筛选、工位登出日志记录
    - 新增PLC移料指令与产线点位状态管理
4.  **业务流程优化**:
    - 调整BOM物料删除校验逻辑,优化工单备料计算
    - 补充物料图号、检测单号等追溯字段
    - 完善工艺流程图与工位绑定关系说明
    - 优化前端页面文案与交互细节
5.  **代码规范与维护**:
    - 新增通用工具函数与前端静态资源
    - 整理路由权限与中间件逻辑
    - 修复部分接口与配置的不兼容问题
2026-09-10 16:59:15 +08:00

167 lines
5.5 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)
}
}
// stationsHandler 代理 MES GET /stations(工位数以数据库为准,不写死 12),原样透传响应体。
func stationsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, _, err := ctx.Mes.Get("/stations", nil)
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)
}