Files
bj_power/bj_power_workstation/internal/handler/report.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

166 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 (
"encoding/json"
"fmt"
"net/http"
"bj_power_workstation/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
// mesRespCode 解析 MES 统一响应 {code,message}。
// MES 业务失败约定为 HTTP 200 + code!=0httpx.Fail),HTTP 层无法区分,必须显式解析业务码。
// code==0 返回空串;code!=0 返回 message(供调用方直接透传给前端)。
func mesRespCode(respBody []byte) string {
if len(respBody) == 0 {
return ""
}
var mr struct {
Code int `json:"code"`
Message string `json:"message"`
}
if err := json.Unmarshal(respBody, &mr); err != nil {
return ""
}
if mr.Code == 0 {
return ""
}
if mr.Message != "" {
return mr.Message
}
return fmt.Sprintf("MES 拒绝本次操作(业务码 %d)", mr.Code)
}
// processDoneHandler POST /api/report/process-done
// {sn, orderNo, processCode, stationNo, steps:[{stepId,name,value,text}], binds:[{materialCode,bindValue}], operator}
// 流程:先冲刷一轮积压 → 实时报 MES /api/internal/station/report
// MES 不可达则写入 report_queue(kind=process_done) 并返回 data.queued=true(离线缓存稍后自动重传);
// MES 业务拒绝(HTTP200+code!=0,如装配物料未绑齐/电气件SN重复)直接失败回前端,绝不入离线队。
// binds 为本次工序的装机绑定(结构件批次/电气件SN),MES 端落库 + 齐套强校验;离线重传时原样携带。
func processDoneHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
ProcessCode int `json:"processCode"`
StationNo int `json:"stationNo"`
Steps []struct {
StepId int `json:"stepId"`
Name string `json:"name"`
Value float64 `json:"value"`
Text string `json:"text"`
} `json:"steps"`
Binds []struct {
MaterialCode string `json:"materialCode"`
BindValue string `json:"bindValue"`
} `json:"binds"`
Operator string `json:"operator"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Sn == "" || req.ProcessCode <= 0 || req.StationNo <= 0 {
fail(w, http.StatusBadRequest, "sn/processCode/stationNo 必填")
return
}
// 先尽力冲刷历史积压,失败不影响本次上报
ctx.Syncer.RunOnce(r.Context())
body := map[string]any{
"sn": req.Sn,
"orderNo": req.OrderNo,
"processCode": req.ProcessCode,
"stationNo": req.StationNo,
"steps": req.Steps,
"operator": req.Operator,
}
if len(req.Binds) > 0 {
body["binds"] = req.Binds
}
payloadBytes, _ := json.Marshal(body)
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "process_done",
"工序完成上报 sn="+req.Sn+" stationNo="+itoa(req.StationNo)+" processCode="+itoa(req.ProcessCode)+" binds="+itoa(len(req.Binds)))
respBody, _, err := ctx.Mes.Post("/api/internal/station/report", json.RawMessage(payloadBytes))
if err != nil {
if qerr := ctx.Store.InsertQueueItem("process_done", string(payloadBytes)); qerr != nil {
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
return
}
logx.Errorf("工序完成上报失败已离线排队: %v", err)
ok(w, map[string]any{"queued": true})
return
}
// MES 业务拒绝(HTTP200+code!=0):缺料/SN冲突等,直接失败,不入队
if msg := mesRespCode(respBody); msg != "" {
logx.Errorf("工序完成上报被 MES 拒绝: %s", msg)
fail(w, http.StatusConflict, msg)
return
}
ok(w, map[string]any{"queued": false})
}
}
// tempStoreHandler POST /api/report/temp-store
// {sn, orderNo, stationNo, processCode, operator} → 调 MES /api/internal/station/checkin(半成品入库)。
// 不可达时以同样 payload 写入 report_queue(kind=temp_store)。
func tempStoreHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
OrderNo string `json:"orderNo"`
StationNo int `json:"stationNo"`
ProcessCode int `json:"processCode"`
Operator string `json:"operator"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Sn == "" {
fail(w, http.StatusBadRequest, "缺少 sn")
return
}
// 先尽力冲刷历史积压,失败不影响本次上报
ctx.Syncer.RunOnce(r.Context())
body := map[string]any{
"sn": req.Sn,
"orderNo": req.OrderNo,
"stationNo": req.StationNo,
"processCode": req.ProcessCode,
"operator": req.Operator,
}
payloadBytes, _ := json.Marshal(body)
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "temp_store",
"暂存/退回库房 sn="+req.Sn+" stationNo="+itoa(req.StationNo))
respBody, _, err := ctx.Mes.Post("/api/internal/station/checkin", json.RawMessage(payloadBytes))
if err != nil {
if qerr := ctx.Store.InsertQueueItem("temp_store", string(payloadBytes)); qerr != nil {
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
return
}
logx.Errorf("暂存退库上报失败已离线排队: %v", err)
ok(w, map[string]any{"queued": true})
return
}
// MES 业务拒绝(HTTP200+code!=0)→ 直接失败,不入队
if msg := mesRespCode(respBody); msg != "" {
logx.Errorf("暂存退库被 MES 拒绝: %s", msg)
fail(w, http.StatusConflict, msg)
return
}
ok(w, map[string]any{"queued": false})
}
}