1. 新增SSE看板广播机制,业务操作后主动推送刷新事件 2. 新增产线工序横道图与工位状态展示面板 3. 移除dashboard无用的three.js依赖 4. 重构WMS客户端布局与WMS前端资源哈希 5. 新增工位终端时钟图标与大屏自适应布局 6. 新增系统截图脚本与说明书生成工具 7. 修复多处代码细节与空值处理逻辑
104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"bj_power_mes/internal/svc"
|
|
)
|
|
|
|
// Service 业务逻辑服务(handler 薄层调用)
|
|
type Service struct {
|
|
ctx *svc.ServiceContext
|
|
}
|
|
|
|
func New(s *svc.ServiceContext) *Service {
|
|
return &Service{ctx: s}
|
|
}
|
|
|
|
// notifyDashboard 生产业务数据落库成功后广播看板刷新事件。
|
|
// SSE 事件只做"数据变了"的通知(空载荷),大屏收到后自行拉取最新快照,
|
|
// 避免把整份快照塞进事件流。无订阅客户端时 Publish 为空操作,零开销。
|
|
func (s *Service) notifyDashboard() {
|
|
if s.ctx.SSE != nil {
|
|
s.ctx.SSE.Publish("dashboard.updated", "{}")
|
|
}
|
|
}
|
|
|
|
// cachedTyped 泛型版缓存:与 cached 同为 TTL + SETNX 防击穿,
|
|
// 区别是返回强类型,调用方不必再做类型断言(看板快照等结构化数据用这个)。
|
|
func cachedTyped[T any](ctx context.Context, s *Service, key string, ttl int, build func() (T, error)) (T, error) {
|
|
if s.ctx.RedisClient == nil {
|
|
return build()
|
|
}
|
|
if v, err := s.ctx.RedisClient.Get(key); err == nil && v != "" {
|
|
var out T
|
|
if json.Unmarshal([]byte(v), &out) == nil {
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
lockKey := key + ":lock"
|
|
ok, _ := s.ctx.RedisClient.SetnxEx(lockKey, "1", 10)
|
|
if !ok {
|
|
for i := 0; i < 10; i++ {
|
|
time.Sleep(100 * time.Millisecond)
|
|
if v, err := s.ctx.RedisClient.Get(key); err == nil && v != "" {
|
|
var out T
|
|
if json.Unmarshal([]byte(v), &out) == nil {
|
|
return out, nil
|
|
}
|
|
}
|
|
}
|
|
return build()
|
|
}
|
|
defer func() { _, _ = s.ctx.RedisClient.Del(lockKey) }()
|
|
|
|
data, err := build()
|
|
if err == nil {
|
|
if b, e := json.Marshal(data); e == nil {
|
|
_ = s.ctx.RedisClient.Setex(key, string(b), ttl)
|
|
}
|
|
}
|
|
return data, err
|
|
}
|
|
|
|
// cached 看板缓存:TTL + 防击穿(SETNX 锁),同一 key 同时仅一个打到 DB
|
|
func (s *Service) cached(ctx context.Context, key string, ttl int, build func() (any, error)) (any, error) {
|
|
if s.ctx.RedisClient == nil {
|
|
return build()
|
|
}
|
|
if v, err := s.ctx.RedisClient.Get(key); err == nil && v != "" {
|
|
var out any
|
|
if json.Unmarshal([]byte(v), &out) == nil {
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
lockKey := key + ":lock"
|
|
ok, _ := s.ctx.RedisClient.SetnxEx(lockKey, "1", 10)
|
|
if !ok {
|
|
// 有人正在重建缓存:短暂等待后重读
|
|
for i := 0; i < 10; i++ {
|
|
time.Sleep(100 * time.Millisecond)
|
|
if v, err := s.ctx.RedisClient.Get(key); err == nil && v != "" {
|
|
var out any
|
|
if json.Unmarshal([]byte(v), &out) == nil {
|
|
return out, nil
|
|
}
|
|
}
|
|
}
|
|
return build()
|
|
}
|
|
defer func() { _, _ = s.ctx.RedisClient.Del(lockKey) }()
|
|
|
|
data, err := build()
|
|
if err == nil {
|
|
if b, e := json.Marshal(data); e == nil {
|
|
_ = s.ctx.RedisClient.Setex(key, string(b), ttl)
|
|
}
|
|
}
|
|
return data, err
|
|
}
|