feat: 装配线看板修正、统一密钥Hardman_2026、操作日志追溯、日志按天存储
Dashboard 移除 CNC/清洗机等不存在设备,改为纯装配线看板(扫码枪+拧紧枪+人工);JWT/内部token统一为 Hardman_2026 写入各 etc/*.yaml,MES 建表 DSN 改读 yaml;event_log 增加 workOrderNo/operator,BOM/备料/PLC下发/拧紧/扫码报工/半成品/AGV 全链路埋点,日志页支持工单号/操作人筛选;MES/WMS 日志按天存储(dailyWriter),WMS SetWriter 用 logx.NewWriter 适配
This commit is contained in:
@@ -54,7 +54,7 @@ type Config struct {
|
||||
// WMS 服务(库存系统 C)
|
||||
Wms struct {
|
||||
BaseURL string `json:",default=http://127.0.0.1:8890"`
|
||||
Token string `json:",default=bj-power-internal-2026"` // X-API-TOKEN 写死内部 token
|
||||
Token string `json:",default=Hardman_2026"` // X-API-TOKEN 写死内部 token
|
||||
}
|
||||
|
||||
// 海康 RCS-2000 AGV 调度
|
||||
@@ -68,7 +68,7 @@ type Config struct {
|
||||
|
||||
// 项目间内部 API(工位终端 D 等调用)写死 token
|
||||
Internal struct {
|
||||
Token string `json:",default=bj-power-internal-2026"`
|
||||
Token string `json:",default=Hardman_2026"`
|
||||
}
|
||||
|
||||
Mom MomConfig `json:"MOM"`
|
||||
|
||||
@@ -54,6 +54,15 @@ const (
|
||||
// 连接状态事件
|
||||
EventConnectionStatusUpdated EventType = "CONNECTION_STATUS_UPDATED"
|
||||
|
||||
// 装配线操作日志事件(工单/工件/操作步骤/人员可追溯)
|
||||
EventBomSaved EventType = "BOM_SAVED"
|
||||
EventMaterialRequested EventType = "MATERIAL_REQUESTED"
|
||||
EventPlcProcessSent EventType = "PLC_PROCESS_SENT"
|
||||
EventTorqueReported EventType = "TORQUE_REPORTED"
|
||||
EventScanReported EventType = "SCAN_REPORTED"
|
||||
EventSemiFlow EventType = "SEMI_FLOW"
|
||||
EventAgvTaskCreated EventType = "AGV_TASK_CREATED"
|
||||
|
||||
EventAll EventType = "*"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,11 +3,28 @@ package eventbus
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 事件构造辅助函数
|
||||
|
||||
// NewProductionEvent 构建装配线操作日志事件(payload 内可含
|
||||
// workOrderNo/orderNo、workpieceNo/sn、operator、stepCode 等键,写入事件日志表)
|
||||
func NewProductionEvent(eventType EventType, description string, payload map[string]any) Event {
|
||||
if payload == nil {
|
||||
payload = map[string]any{}
|
||||
}
|
||||
payload["description"] = description
|
||||
return Event{
|
||||
ID: fmt.Sprintf("%s-%d", strings.ToLower(string(eventType)), time.Now().UnixNano()),
|
||||
Type: eventType,
|
||||
Source: "production",
|
||||
Timestamp: time.Now(),
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTempSlotChangedEvent 构建暂存台槽位变更事件
|
||||
func NewTempSlotChangedEvent(slotNo, jobID int, action string) Event {
|
||||
return Event{
|
||||
|
||||
@@ -157,6 +157,17 @@ func (w *EventLogWriter) flush(events []eventbus.Event) {
|
||||
if v, ok := e.Payload["workpieceNo"].(string); ok && v != "" {
|
||||
b.SetWorkpieceNo(v)
|
||||
}
|
||||
if v, ok := e.Payload["sn"].(string); ok && v != "" && e.Payload["workpieceNo"] == nil {
|
||||
b.SetWorkpieceNo(v) // 工位终端上报以 sn 为工件号
|
||||
}
|
||||
if v, ok := e.Payload["workOrderNo"].(string); ok && v != "" {
|
||||
b.SetWorkOrderNo(v)
|
||||
} else if v, ok := e.Payload["orderNo"].(string); ok && v != "" {
|
||||
b.SetWorkOrderNo(v)
|
||||
}
|
||||
if v, ok := e.Payload["operator"].(string); ok && v != "" {
|
||||
b.SetOperator(v)
|
||||
}
|
||||
if v, ok := getPayloadInt(e.Payload, "equipmentId"); ok {
|
||||
b.SetEquipmentId(v)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent/agvtask"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
@@ -81,6 +82,16 @@ func (l *AgvTaskLogic) AgvTaskCreate(req *types.AgvTaskCreateReq) (map[string]an
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 操作日志(AGV 搬运可追溯)
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventAgvTaskCreated,
|
||||
fmt.Sprintf("AGV 任务 %s %s→%s [%s]", taskNo, req.DockFrom, req.DockTo, status),
|
||||
map[string]any{
|
||||
"orderNo": req.OrderNo, "taskNo": taskNo, "status": status,
|
||||
"dockFrom": req.DockFrom, "dockTo": req.DockTo,
|
||||
"operator": CurrentUserName(l.ctx, l.svcCtx),
|
||||
}))
|
||||
|
||||
if status == "执行中" && sim {
|
||||
go func(id int, orderNo string) {
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
@@ -2,9 +2,11 @@ package production
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/bomitem"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
|
||||
@@ -81,5 +83,16 @@ func (l *BomLogic) BomSave(req *types.BomSaveReq) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
// 操作日志(BOM 维护可追溯)
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventBomSaved,
|
||||
fmt.Sprintf("保存产品 %s 的 BOM(%d 项物料)", req.ProductCode, len(req.Items)),
|
||||
map[string]any{
|
||||
"productCode": req.ProductCode, "itemCount": len(req.Items),
|
||||
"operator": CurrentUserName(l.ctx, l.svcCtx),
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"bj_power_mes/ent/materialrequest"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
wmsstock "bj_power_mes/internal/wmsstock"
|
||||
@@ -128,6 +129,20 @@ func (l *MaterialRequestLogic) MaterialRequestCreate(req *types.MaterialRequestC
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 操作日志(备料可追溯)
|
||||
operator := req.Operator
|
||||
if operator == "" {
|
||||
operator = CurrentUserName(l.ctx, l.svcCtx)
|
||||
}
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventMaterialRequested,
|
||||
fmt.Sprintf("备料 %s(%s),物料 %d 项", req.OrderNo, status, len(result.Items)),
|
||||
map[string]any{
|
||||
"orderNo": req.OrderNo, "status": status, "allOk": result.AllOk,
|
||||
"operator": operator,
|
||||
}))
|
||||
|
||||
out := map[string]any{"record": rec}
|
||||
if respText != "" {
|
||||
out["warn"] = respText
|
||||
|
||||
@@ -2,9 +2,11 @@ package production
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/plcsendlog"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/plcproc"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
@@ -29,6 +31,14 @@ func (l *PlcLogic) PlcSendProcess(req *types.PlcSendProcessReq) (*ent.PlcSendLog
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 操作日志
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventPlcProcessSent,
|
||||
fmt.Sprintf("PLC 下发工序号 %s → %s", req.CodeStr, req.OrderNo),
|
||||
map[string]any{
|
||||
"orderNo": req.OrderNo, "codeStr": req.CodeStr,
|
||||
"operator": CurrentUserName(l.ctx, l.svcCtx),
|
||||
}))
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventDashboardUpdate, `{"type":"plc_sent","orderNo":"`+req.OrderNo+`","code":"`+req.CodeStr+`"}`)
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package production
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"bj_power_mes/ent/processdict"
|
||||
"bj_power_mes/ent/scanrecord"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
@@ -58,6 +60,19 @@ func (l *ScanLogic) ScanReport(req *types.ScanReportReq) (map[string]any, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operator := req.Operator
|
||||
if operator == "" {
|
||||
operator = CurrentUserName(l.ctx, l.svcCtx)
|
||||
}
|
||||
// 操作日志(工单/工件/步骤/人员可追溯)
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventScanReported,
|
||||
fmt.Sprintf("扫码报工[%s] 工序%s 工件%s", action, req.StepCode, req.Sn),
|
||||
map[string]any{
|
||||
"orderNo": req.OrderNo, "sn": req.Sn, "stepCode": req.StepCode,
|
||||
"action": action, "dockCode": req.DockCode, "operator": operator,
|
||||
}))
|
||||
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventDashboardUpdate,
|
||||
map[string]any{
|
||||
"type": "scan_reported", "orderNo": req.OrderNo, "sn": req.Sn,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"bj_power_mes/ent/semiflow"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
@@ -85,6 +86,14 @@ func (l *SemiFlowLogic) record(action string, req *types.SemiFlowReq, zone strin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 操作日志(半成品流转可追溯)
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventSemiFlow,
|
||||
fmt.Sprintf("半成品%s 工件%s → %s", map[string]string{"inbound": "入库", "outbound": "出库"}[action], req.Sn, zone),
|
||||
map[string]any{
|
||||
"sn": req.Sn, "orderNo": req.OrderNo, "action": action,
|
||||
"zoneCode": zone, "completedProcess": req.CompletedProcess, "operator": req.Operator,
|
||||
}))
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventDashboardUpdate,
|
||||
map[string]any{"type": "semi_flow", "action": action, "sn": req.Sn, "orderNo": req.OrderNo})
|
||||
return map[string]any{"record": rec}, nil
|
||||
|
||||
@@ -2,8 +2,10 @@ package production
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"bj_power_mes/ent/torquerecord"
|
||||
"bj_power_mes/internal/eventbus"
|
||||
"bj_power_mes/internal/sse"
|
||||
"bj_power_mes/internal/svc"
|
||||
"bj_power_mes/internal/types"
|
||||
@@ -60,6 +62,15 @@ func (l *TorqueLogic) TorqueReport(req *types.TorqueReportReq) (map[string]any,
|
||||
return nil, err
|
||||
}
|
||||
saved++
|
||||
|
||||
// 操作日志(拧紧结果可追溯)
|
||||
l.svcCtx.EventBus.Publish(l.ctx, eventbus.NewProductionEvent(
|
||||
eventbus.EventTorqueReported,
|
||||
fmt.Sprintf("拧紧上报 %s 螺栓%s %s 扭矩%.1f°角度%.1f", rec.Sn, rec.ScrewNo, result, rec.Torque, rec.Angle),
|
||||
map[string]any{
|
||||
"workOrderNo": rec.WorkOrderNo, "sn": rec.Sn, "dockCode": rec.DockCode,
|
||||
"screwNo": rec.ScrewNo, "result": result, "operator": rec.Operator,
|
||||
}))
|
||||
}
|
||||
|
||||
l.svcCtx.SSEHandler.Emit(sse.EventDashboardUpdate,
|
||||
|
||||
@@ -46,6 +46,12 @@ func (l *QueryEventLogsLogic) QueryEventLogs(req *types.QueryEventLogsReq) (resp
|
||||
if req.WorkpieceNo != "" {
|
||||
conditions = append(conditions, enteventlog.WorkpieceNoContains(req.WorkpieceNo))
|
||||
}
|
||||
if req.WorkOrderNo != "" {
|
||||
conditions = append(conditions, enteventlog.WorkOrderNoContains(req.WorkOrderNo))
|
||||
}
|
||||
if req.Operator != "" {
|
||||
conditions = append(conditions, enteventlog.OperatorContains(req.Operator))
|
||||
}
|
||||
if req.JobId > 0 {
|
||||
conditions = append(conditions, enteventlog.JobId(req.JobId))
|
||||
}
|
||||
@@ -87,6 +93,8 @@ func (l *QueryEventLogsLogic) QueryEventLogs(req *types.QueryEventLogsReq) (resp
|
||||
CreatedAt: lg.CreatedAt.Format(time.RFC3339),
|
||||
JobId: int(lg.JobId),
|
||||
WorkpieceNo: lg.WorkpieceNo,
|
||||
WorkOrderNo: lg.WorkOrderNo,
|
||||
Operator: lg.Operator,
|
||||
EquipmentId: int(lg.EquipmentId),
|
||||
EquipmentName: lg.EquipmentName,
|
||||
TempSlotNo: int(lg.TempSlotNo),
|
||||
|
||||
@@ -1010,6 +1010,8 @@ type EventLog struct {
|
||||
CreatedAt string `json:"createdAt"`
|
||||
JobId int `json:"jobId,optional"`
|
||||
WorkpieceNo string `json:"workpieceNo,optional"`
|
||||
WorkOrderNo string `json:"workOrderNo,optional"`
|
||||
Operator string `json:"operator,optional"`
|
||||
EquipmentId int `json:"equipmentId,optional"`
|
||||
EquipmentName string `json:"equipmentName,optional"`
|
||||
TempSlotNo int `json:"tempSlotNo,optional"`
|
||||
@@ -1027,6 +1029,8 @@ type QueryEventLogsReq struct {
|
||||
StartTime string `form:"startTime,optional"`
|
||||
EndTime string `form:"endTime,optional"`
|
||||
WorkpieceNo string `form:"workpieceNo,optional"`
|
||||
WorkOrderNo string `form:"workOrderNo,optional"`
|
||||
Operator string `form:"operator,optional"`
|
||||
JobId int `form:"jobId,optional"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user