feat: 五项目业务实现并对接完成
- 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
This commit is contained in:
@@ -0,0 +1,432 @@
|
||||
package production
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
errx "bj_power_mes/common/errorx"
|
||||
"bj_power_mes/internal/logic/dashboard"
|
||||
"bj_power_mes/internal/logic/production"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
func invalidParams() error { return errx.New(errx.RequestParamInvalid) }
|
||||
|
||||
// ============ 内部解析工具(不依赖 goctl 生成代码)============
|
||||
|
||||
func decodeJSON(r *http.Request, v any) bool {
|
||||
defer r.Body.Close()
|
||||
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ok(w http.ResponseWriter, r *http.Request, data any) {
|
||||
httpx.OkJsonCtx(r.Context(), w, data)
|
||||
}
|
||||
|
||||
func fail(w http.ResponseWriter, r *http.Request, err error) {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
}
|
||||
|
||||
func qInt(r *http.Request, name string, def int) int {
|
||||
v := r.URL.Query().Get(name)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ============ BOM 管理 ============
|
||||
|
||||
func BomListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := production.NewBomLogic(r.Context(), svcCtx)
|
||||
resp, err := l.BomList(&types.BomListReq{ProductCode: r.URL.Query().Get("productCode")})
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func BomSaveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.BomSaveReq
|
||||
if !decodeJSON(r, &req) {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
l := production.NewBomLogic(r.Context(), svcCtx)
|
||||
if err := l.BomSave(&req); err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, map[string]any{"saved": len(req.Items)})
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 工序字典 ============
|
||||
|
||||
func ProcessDictListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := production.NewProcessDictLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ProcessDictList()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessDictSaveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.ProcessDictSaveReq
|
||||
if !decodeJSON(r, &req) {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
l := production.NewProcessDictLogic(r.Context(), svcCtx)
|
||||
if err := l.ProcessDictSave(&req); err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, map[string]any{"saved": len(req.Items)})
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 备料单 ============
|
||||
|
||||
func MaterialRequestCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.MaterialRequestCreateReq
|
||||
if !decodeJSON(r, &req) || req.OrderNo == "" {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
req.Operator = production.CurrentUserName(r.Context(), svcCtx)
|
||||
l := production.NewMaterialRequestLogic(r.Context(), svcCtx)
|
||||
resp, err := l.MaterialRequestCreate(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func MaterialRequestListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.MaterialRequestQueryReq{
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Page: qInt(r, "page", 1),
|
||||
Size: qInt(r, "size", 20),
|
||||
}
|
||||
l := production.NewMaterialRequestLogic(r.Context(), svcCtx)
|
||||
resp, err := l.MaterialRequestList(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ PLC 工序下发 ============
|
||||
|
||||
func PlcSendProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.PlcSendProcessReq
|
||||
if !decodeJSON(r, &req) || req.CodeStr == "" {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
l := production.NewPlcLogic(r.Context(), svcCtx)
|
||||
resp, err := l.PlcSendProcess(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func PlcSendLogsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.PlcSendLogQueryReq{
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Limit: qInt(r, "limit", 50),
|
||||
}
|
||||
l := production.NewPlcLogic(r.Context(), svcCtx)
|
||||
resp, err := l.PlcSendLogs(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 拧紧记录(工位终端上报)============
|
||||
|
||||
func TorqueReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.TorqueReportReq
|
||||
if !decodeJSON(r, &req) {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
l := production.NewTorqueLogic(r.Context(), svcCtx)
|
||||
resp, err := l.TorqueReport(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TorqueQueryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.TorqueQueryReq{
|
||||
Sn: r.URL.Query().Get("sn"),
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Page: qInt(r, "page", 1),
|
||||
Size: qInt(r, "size", 20),
|
||||
}
|
||||
l := production.NewTorqueLogic(r.Context(), svcCtx)
|
||||
resp, err := l.TorqueQuery(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 扫码报工 ============
|
||||
|
||||
func ScanReportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.ScanReportReq
|
||||
if !decodeJSON(r, &req) || req.Sn == "" {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
if req.Operator == "" {
|
||||
req.Operator = production.CurrentUserName(r.Context(), svcCtx)
|
||||
}
|
||||
l := production.NewScanLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ScanReport(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func ScanQueryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.ScanQueryReq{
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Sn: r.URL.Query().Get("sn"),
|
||||
Page: qInt(r, "page", 1),
|
||||
Size: qInt(r, "size", 20),
|
||||
}
|
||||
l := production.NewScanLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ScanQuery(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 半成品流转 ============
|
||||
|
||||
func SemiInboundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SemiFlowReq
|
||||
if !decodeJSON(r, &req) || req.Sn == "" {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
if req.Operator == "" {
|
||||
req.Operator = production.CurrentUserName(r.Context(), svcCtx)
|
||||
}
|
||||
l := production.NewSemiFlowLogic(r.Context(), svcCtx)
|
||||
resp, err := l.SemiInbound(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func SemiOutboundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SemiFlowReq
|
||||
if !decodeJSON(r, &req) || req.Sn == "" {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
if req.Operator == "" {
|
||||
req.Operator = production.CurrentUserName(r.Context(), svcCtx)
|
||||
}
|
||||
l := production.NewSemiFlowLogic(r.Context(), svcCtx)
|
||||
resp, err := l.SemiOutbound(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func SemiFlowsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.SemiFlowQueryReq{
|
||||
Sn: r.URL.Query().Get("sn"),
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Page: qInt(r, "page", 1),
|
||||
Size: qInt(r, "size", 20),
|
||||
}
|
||||
l := production.NewSemiFlowLogic(r.Context(), svcCtx)
|
||||
resp, err := l.SemiFlows(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ AGV 任务 ============
|
||||
|
||||
func AgvTaskCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.AgvTaskCreateReq
|
||||
if !decodeJSON(r, &req) {
|
||||
fail(w, r, invalidParams())
|
||||
return
|
||||
}
|
||||
l := production.NewAgvTaskLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AgvTaskCreate(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func AgvTaskListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.AgvTaskQueryReq{
|
||||
Status: r.URL.Query().Get("status"),
|
||||
OrderNo: r.URL.Query().Get("orderNo"),
|
||||
Page: qInt(r, "page", 1),
|
||||
Size: qInt(r, "size", 20),
|
||||
}
|
||||
l := production.NewAgvTaskLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AgvTaskList(&req)
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 追溯 ============
|
||||
|
||||
func TraceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
l := production.NewTraceLogic(r.Context(), svcCtx)
|
||||
resp, err := l.Trace(q.Get("sn"), q.Get("orderNo"))
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 看板(内部 API:Dashboard A 免登录拉取)============
|
||||
|
||||
func DashboardOverviewInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := dashboard.NewDashboardOverviewLogic(r.Context(), svcCtx)
|
||||
resp, err := l.DashboardOverview()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 看板明细内部 API(设备/进度/报警/趋势)============
|
||||
|
||||
func DashboardEquipmentInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := dashboard.NewSnapshotLogic(r.Context(), svcCtx)
|
||||
resp, err := l.EquipmentList()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func DashboardProgressInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := dashboard.NewSnapshotLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ProgressList()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func DashboardAlarmsInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := dashboard.NewSnapshotLogic(r.Context(), svcCtx)
|
||||
resp, err := l.AlarmList()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func DashboardTrendsInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := dashboard.NewSnapshotLogic(r.Context(), svcCtx)
|
||||
resp, err := l.TrendList()
|
||||
if err != nil {
|
||||
fail(w, r, err)
|
||||
return
|
||||
}
|
||||
ok(w, r, resp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
productionHandler "bj_power_mes/internal/handler/production"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
/*
|
||||
RegisterProductionRoutes 注册生产业务扩展路由(BOM/工序字典/备料/PLC/拧紧/报工/半成品/AGV/追溯/看板内部API)。
|
||||
不复用 goctl 生成的 routes.go,独立注册以便手写路由长期维护。
|
||||
- /api/v1/* :JWT 保护,前端 MES 页面使用
|
||||
- /api/internal/* :X-API-TOKEN 写死 token,供 WMS/工位终端/Dashboard 服务间调用
|
||||
*/
|
||||
func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
// ---------- 内部 API(写死 token) ----------
|
||||
internal := func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
expect := serverCtx.Config.Internal.Token
|
||||
if expect == "" || r.Header.Get("X-API-TOKEN") != expect {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"code":401,"message":"内部接口 token 错误"}`))
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/internal/tightening/report",
|
||||
Handler: internal(productionHandler.TorqueReportHandler(serverCtx)),
|
||||
})
|
||||
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/internal/dashboard/overview",
|
||||
Handler: internal(productionHandler.DashboardOverviewInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/internal/dashboard/equipment",
|
||||
Handler: internal(productionHandler.DashboardEquipmentInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/internal/dashboard/progress",
|
||||
Handler: internal(productionHandler.DashboardProgressInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/internal/dashboard/alarms",
|
||||
Handler: internal(productionHandler.DashboardAlarmsInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/internal/dashboard/trends",
|
||||
Handler: internal(productionHandler.DashboardTrendsInternalHandler(serverCtx)),
|
||||
})
|
||||
|
||||
// ---------- JWT 业务 API ----------
|
||||
jwtRoutes := []rest.Route{
|
||||
{Method: http.MethodGet, Path: "/bom", Handler: productionHandler.BomListHandler(serverCtx)},
|
||||
{Method: http.MethodPut, Path: "/bom", Handler: productionHandler.BomSaveHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/process-dict", Handler: productionHandler.ProcessDictListHandler(serverCtx)},
|
||||
{Method: http.MethodPut, Path: "/process-dict", Handler: productionHandler.ProcessDictSaveHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/material-requests", Handler: productionHandler.MaterialRequestCreateHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/material-requests", Handler: productionHandler.MaterialRequestListHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/plc/send-process", Handler: productionHandler.PlcSendProcessHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/plc/send-logs", Handler: productionHandler.PlcSendLogsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/scan/report", Handler: productionHandler.ScanReportHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/scan/records", Handler: productionHandler.ScanQueryHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/semi/inbound", Handler: productionHandler.SemiInboundHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/semi/outbound", Handler: productionHandler.SemiOutboundHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/semi/flows", Handler: productionHandler.SemiFlowsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/agv/tasks", Handler: productionHandler.AgvTaskCreateHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/agv/tasks", Handler: productionHandler.AgvTaskListHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/trace", Handler: productionHandler.TraceHandler(serverCtx)},
|
||||
}
|
||||
server.AddRoutes(jwtRoutes,
|
||||
rest.WithPrefix("/api/v1"),
|
||||
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user