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:
SunYF
2026-08-28 09:22:19 +08:00
parent a2afd2f6dc
commit 450a094f0e
39 changed files with 516 additions and 2111 deletions
+2 -2
View File
@@ -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"`
+9
View File
@@ -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 = "*"
)
+17
View File
@@ -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{
+11
View File
@@ -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)
+14 -1
View File
@@ -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),
+4
View File
@@ -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"`
}