|
|
b91a366f51
|
Merge branch 'master' into feat/multi-tenant
|
2026-04-22 18:23:50 +08:00 |
|
|
|
3cfd342318
|
fix(ops): BADGE 绑定/解绑后即时同步工牌缓存
问题:
1. 设备先上线、后绑定为 BADGE 时,"可分配工牌"列表迟迟不出现该设备;
2. 已绑定区域的设备收不到工单(被分派策略过滤掉)。
根因:实时上线路径 BadgeDeviceStatusEventHandler.onMessage 在写
ops:badge:status:{deviceId} Redis 缓存前,先 isBadgeDevice() 校验
ops_area_device_relation 是否存在 BADGE 关系。设备如果在绑定前就上线,
事件被丢弃;建立关系后又没有任何机制回填 Redis,得等 5/30 分钟的
BadgeDeviceStatusSyncJob 对账才会被发现(线上日志可见 deviceId=58 在
绑定后 30 分钟才被对账修正:reason=定时对账修正-上线)。
解绑同样有反向缺口:关系记录删了但 Redis 缓存得等 24h TTL 自然过期,
期间 listAvailableBadges 仍可能返回该设备。
修复:在 ops-biz 引入 AreaDeviceBoundEvent / AreaDeviceUnboundEvent,
bindDevice / unbindDevice 成功后通过 ApplicationEventPublisher 发布;
environment-biz 新增 BadgeAreaBoundEventListener 仅订阅 BADGE 类型,
使用 @TransactionalEventListener(AFTER_COMMIT) + @Async 确保事务提交后
异步执行不阻塞接口:
- 绑定:单次调 IotDeviceQueryApi.getDevice 取齐 state + nickname +
deviceName,根据状态写 Redis(IDLE/OFFLINE);
- 解绑:直接调 BadgeDeviceStatusService.deleteBadgeStatus 清理 Redis。
依赖方向遵循 environment-biz → ops-biz;ops-biz 不反向依赖条线模块,
通过事件解耦,与现有 OrderStateChangedEvent 模式一致。
测试:
- AreaDeviceRelationServiceTest 补 iotDeviceQueryApi mock 让原本因缺
mock 而 RED 的 testBindDevice_TrafficCounter_Success 转绿;
- 新增对 bound / unbound 事件 publishEvent 调用的 verify。
- 全套 10/10 通过。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:04:47 +08:00 |
|
|
|
6c4153fe23
|
fix(ops): 手动派单提前写执行人字段,修复按键报"没有工单"
问题:管理员手动派单后,工牌按键查询持续返回"没有工单",TTS 循环播报"工单
来啦"但用户无法响应(线上日志可见 deviceId=58 一段时间内连续 8 次按键查询
全部命中 CleanOrderAuditEventHandler.handleQueryEvent 的"没有工单"分支)。
根因:ManualOrderActionFacade.dispatch() 原顺序是
1. transition() —— 事务内同步发布 OrderStateChangedEvent,
BadgeDeviceStatusEventListener.onOrderStateChanged 重新 selectById 拿
assigneeId 决定是否写 Redis 工单关联;
2. update assigneeId / assigneeName。
第 1 步事件触发时 assigneeId 仍是 null,listener 走"工单未关联设备,跳过
处理"分支,Redis ops:badge:status:{deviceId} 的 currentOpsOrderId 永远不
会写入;同时主表 assigneeDeviceId 也始终为 null,
CleanOrderAuditEventHandler.handleQueryEvent 用
"WHERE assignee_device_id=?" 查工单永远落空 → "没有工单"。
修复:把执行人字段更新前置到 transition() 之前,并补 setAssigneeDeviceId
(与 OrderLifecycleManagerImpl.dispatch() 自动派单路径一致)。
事件 listener 此时 selectById 拿得到 deviceId,正常落 Redis;audit
查询也命中,按键路径恢复。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:04:10 +08:00 |
|
|
|
8c664a479d
|
refactor(ops): 状态转换成功不再镜像写 bus_log
问题:每次 transition() 成功 commit 后,OrderTransitionAuditListener 都会
向 bus_log 写一条"状态转换成功: X -> Y"的镜像记录。该信息已由各条线
EventListener(CleanOrderEventListener 的 ORDER_DISPATCHED 等)和
ops_order_event 表完整覆盖,bus_log 里这条镜像形成噪声且与业务日志重复,
线上一次工单流转能产出 4-5 条同义日志,运维抓异常时被淹没。
改动:onAfterCommit 在 event.isSuccess()=true 时直接 return;
失败 / 派发被拒(DISPATCH_REJECTED)/ 回滚三类异常路径继续写,
保留运维真正关心的"为什么失败"审计闭环。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:03:51 +08:00 |
|
|
|
65ad3f35e5
|
Merge branch 'master' into feat/multi-tenant
吸收 master 今日 9 个工单链路修复:
- autoDispatchNext/dispatch 空闲兜底 + FOR UPDATE 并发防护
- 状态转换审计闭环(AFTER_COMMIT/AFTER_ROLLBACK)
- 队列楼层权重强优先 + 三级 baseline 兜底 + N+1 优化
- 工牌 nickname 回填
- CleanOrderAutoCancelJob 超时工单自动取消
|
2026-04-20 16:04:46 +08:00 |
|
|
|
ba6f94a279
|
fix(ops): review 复盘补齐 FOR UPDATE 覆盖面 + 清理注解/日志死角
今日 review 发现 Bug #2 的 FOR UPDATE 防线只装在 dispatch() 上,但同文件另有两条
路径绕过它:
1. P1 — DispatchEngineImpl.autoDispatchNext 调 transition() 派发队列下一单,
不走 FOR UPDATE。idle 校验和 transition 之间存在竞争窗口,能再次让同 assignee
挂两条 DISPATCHED。改调 dispatch(),天然继承串行化。
补测 autoDispatchNext_whenDispatchingFromQueue_shouldGoThroughDispatchNotTransition
锁定该不变量。
2. P2 — OrderLifecycleManagerImpl.resumeOrder/resumeInterruptedOrder 同样走
transition(),P0 恢复与并发派发竞争时可能产生两条 DISPATCHED。改为先
selectById 取 assigneeId,改调 dispatch() 让同一检查生效。
顺手清理 3 个误导:
- DispatchEngineImpl.executePushAndEnqueue 原先忽略内部 dispatch 的返回值,
并发场景下会输出假的“已推送等待任务”日志误导运维,改为按 result.isSuccess()
分支打印。
- OrderTransitionAuditListener.writeRollbackAudit 的 @Transactional(REQUIRES_NEW)
是死注解(由 onAfterRollback 自调用,Spring 代理无法拦截;且 AFTER_ROLLBACK
本就无事务),移除并更新 Javadoc 说明实际行为。
- OrderQueueServiceEnhanced.triggerQueueRebuildAfterCommit 的自调用绕过
@Transactional 是设计意图(最终一致即可),补 Javadoc 解释事务边界,
避免后续误判为 bug。
测试:ops-biz 56 个相关用例全部通过,含新增的 P1 锁定测试。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 14:51:32 +08:00 |
|
|
|
9f3ca9c6f2
|
test(ops): 补齐工单链路 5 个修复点的集成测试
与 4d85659…a5f916c 的 5 次修复对齐,用 Mockito 风格覆盖状态链路关键分支:
- DispatchEngineIdleCheckTest:autoDispatchNext 空闲兜底 + executeDispatch
MySQL 活跃态降级(Bug #1/#4),ENQUEUE_ONLY 路径不触发兜底查询避免开销浪费
- DispatchEngineConflictFallbackTest:FOR UPDATE 冲突分支(Bug #2),
PENDING → 降级入队、QUEUED → 保持排队、其他错误码 → 硬失败
- OrderTransitionAuditListenerTest:审计闭环(Bug #7),AFTER_COMMIT 成功/WARN/ERROR
分支 + AFTER_ROLLBACK 强制视为失败 + 7 种目标状态映射
- QueueScoreCalculatorEnhancedTest:楼层权重 G+B,锁死"FLOOR×10 > AGING×240"
不变量,验证 base/target 任一 null → score=0,移除旧 +600 罚分后语义对称
22 个新测试全部通过;模块内 115/117 测试通过,2 个 pre-existing 失败
(VspNotifyClient/AreaDeviceRelation) 依赖外部服务,与本次改动无关。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 14:24:07 +08:00 |
|
|
|
a5f916c62a
|
fix(ops): 队列楼层权重修复——强楼层优先 + 闭环基准兜底 + N+1 优化
问题:楼层差在分数公式中本该主导同优先级排序,但有四个缺陷导致效果不稳:
1. 有 base 无 target 时给 +600 罚分,无 base 时则全免罚——同一工单在
保洁员忙/闲时排序不单调(B)。
2. 基准楼层只在 user 有 PROCESSING 时生效,空闲时完全无楼层信号(A)。
3. enqueue 瞬间 score 不含楼层,要等下一轮 rebuild 才补上(H)。
4. aging 上限 720 > floorDiff 上限 600,等满 4 小时可反超同优先级 10 层差
任务,削弱"强楼层优先"语义(G)。
5. rebuild 内 for 循环对每条 WAITING 单独 selectById(order)+selectById(area),
N+1 问题(F)。
修复:
1. QueueScoreCalculator(B + G)
- FLOOR_WEIGHT 60 → 100:上限 1000 > aging 上限 720,4 小时老化不再反超
同优先级的近楼层任务。
- 删除"有 base 无 target +600"分支:任一侧缺失即 score=0,语义对称。
2. OpsOrderMapper.selectLatestCompletedAreaIdByAssignee(A 二级兜底)
查最近 24h 内已完成工单的 area,用来推断空闲保洁员的物理位置。
超过 24h 视为跨班次、轨迹失效。
3. OrderQueueServiceEnhanced.resolveBaselineAreaId(A 三级兜底)
PROCESSING.area → 最近 24h COMPLETED.area → 调用方传的 fallbackAreaId。
4. OrderQueueServiceEnhanced.enqueue(H)
事务提交后 triggerQueueRebuildAfterCommit(userId, null),新入队工单
立即按楼层差参与排序,不依赖下一次 autoDispatchNext 触发。
5. OrderQueueServiceEnhanced.rebuildWaitingTasksByUserId(F)
批量 selectBatchIds(orders) + selectBatchIds(areas),100 条 WAITING
从 200 次 SELECT 降到 2 次。
权重直观对比(P2=priority×1500=3000):
旧分数 新分数
同层刚入队 3000 3000
差5层刚入队 3000+300=3300 3000+500=3500
差5层等2小时 3000+300-360=2940 3000+500-360=3140
同层等4小时 3000+0-720=2280 3000+0-720=2280
新权重下"差5层等2小时"仍大于"同层刚入队",楼层稳定主导排序;
极端 aging(>4h)仍能让同层任务被近楼层任务压制优先执行。
测试:QueueScoreCalculatorTest(3)、OrderQueueServiceEnhancedTest(1,
已按 selectBatchIds + selectActiveListByUserId 更新 mock)、QueueSyncServiceTest
全绿。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 13:32:24 +08:00 |
|
|
|
3e248fee8c
|
fix(ops): 补齐状态转换审计闭环,回滚场景也留痕到 bus_log
问题:ops_order_event 在主事务内写,事务 rollback 则整段记录消失;
若状态机转换抛异常或并发冲突被拒,线上只有控制台日志而无数据库审计,
运维难以追溯"是谁、在什么时候、尝试做了什么转换、为什么失败"。
设计:中央事件发布 + TransactionalEventListener 双阶段落盘
1. OrderTransitionAttemptedEvent(新)
覆盖 transition 成功、失败、FOR UPDATE 被拒三种情况,携带 orderId、
fromStatus、targetStatus、errorCode、errorMessage、causeSummary 等。
2. OrderLifecycleManagerImpl
- transition 成功分支:publishAttempt(success=true)
- transition 失败分支(context.hasError):publishAttempt(success=false,
errorCode=INVALID_TRANSITION, cause=摘要)
- dispatch FOR UPDATE 命中分支:publishAttempt(success=false,
errorCode=ASSIGNEE_HAS_ACTIVE_ORDER)
publishAttempt 内部 try/catch,审计失败不影响主流程。
3. OrderTransitionAuditListener(新)
- @TransactionalEventListener(AFTER_COMMIT, fallbackExecution=true)
主事务已提交,按事件本身的 success 写 bus_log;INFO 级。
- @TransactionalEventListener(AFTER_ROLLBACK) + @Transactional(REQUIRES_NEW)
主事务已回滚,事件里声称的 success 强制视为失败;独立事务写 bus_log
避免因主事务回滚而日志同样丢失。
- errorCode、fromStatus、targetStatus、reason、cause 全部落 payload。
- 冲突(ASSIGNEE_HAS_ACTIVE_ORDER)→ WARN;其他失败 → ERROR。
4. LogType 新增 TRANSITION_FAILED、DISPATCH_REJECTED。
5. EventLogRecorder 接口补 recordSync(实现类已有同名方法)。
运维查询:按 eventDomain='dispatch' + eventLevel IN ('WARN','ERROR')
即可一眼看出所有"尝试但未成功"的状态转换。errorCode 留在 payload JSON 内,
未升级为一等字段(后续如需聚合统计再迁移)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 13:11:28 +08:00 |
|
|
|
b534d79434
|
fix(ops): 派发入口加 FOR UPDATE 并发兜底,冲突时降级入队避免悬空
业务不变量:同一执行人在任一时刻最多只有 1 条活跃工单
(DISPATCHED/CONFIRMED/ARRIVED)。PAUSED 不纳入——P0 打断恢复
走 PAUSED→DISPATCHED,此处必须放行。
实现:
1. OpsOrderMapper.selectActiveByAssigneeForUpdate
查询 assignee 活跃工单并对命中行加 FOR UPDATE 排他锁。必须在
事务中调用。
2. OrderLifecycleManagerImpl.dispatch 入口校验
事务开启后立即执行 FOR UPDATE 查询,命中则返回带错误码
ASSIGNEE_HAS_ACTIVE_ORDER 的失败结果,不再执行责任链,
事务 commit 空操作、锁释放;并发竞争的第二个线程会阻塞到
第一个 commit 后看到活跃单,失败退出。
3. 新增 TransitionErrorCode 枚举 + OrderTransitionResult.errorCode
调用方可区分需降级的冲突与硬失败,避免把"可降级"的结果
直接抛给用户。
4. DispatchEngineImpl.executeDirectDispatch 降级逻辑
- 冲突 + 原状态 PENDING → 调 executeEnqueueOnly 降级到 QUEUED,
工单不悬空,等下一轮 autoDispatchNext 重挑。
- 冲突 + 原状态已是 QUEUED(并发另一路抢先派发时回滚保留)
→ 返回 fail 但不重复入队,天然等下一轮。
- 其他失败 → 照常 fail。
职责划分:
- 生命周期层负责"拒绝违反不变量的转换"
- 编排层负责"失败后给工单安置归宿"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 11:52:38 +08:00 |
|
|
|
c24b1eb641
|
fix(ops): 直接派发加空闲兜底 + 队列同步按活跃状态过滤
1. 直接派发空闲兜底(补 autoDispatchNext 之外的另一条派发入口)
DispatchEngineImpl.executeDispatch 在 DIRECT_DISPATCH/PUSH_AND_ENQUEUE
前增加 MySQL 兜底校验:若执行人仍挂活跃工单(Redis 判空闲但 MySQL
不一致的场景),强制降级为 ENQUEUE_ONLY 让任务进队列等待下一轮
autoDispatchNext 接力。避免同一设备再次出现并行多单。
2. 队列同步按活跃状态过滤
syncUserQueueToRedis / getTasksByUserId 的 MySQL 回填路径此前调用
selectListByUserId 不过滤状态,会把历史 REMOVED 记录一并同步到
Redis(线上观察到设备 31 的 Redis ZSet 塞了 206 条、其中 205 条是
REMOVED)。新增 OpsOrderQueueMapper.selectActiveListByUserId,只返
回 WAITING/PROCESSING/PAUSED,两条同步链路改走此方法。原 selectList
ByUserId 保留给审计/统计场景。
未清理历史 REMOVED 记录,保留审计追溯。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 11:22:18 +08:00 |
|
|
|
4d85659277
|
fix(ops): 修复同一工牌并行多单的状态错乱
线上观察:管理员手动取消一个僵尸 DISPATCHED 单会引发"越清越多"——
系统顺势派队列首条给仍在工作的保洁员,监听器再用"旧工单残留"机制
尝试取消当前正在执行的工单,该取消走 REQUIRES_NEW 独立事务且吞异常,
最终新单落地、旧单残留,同一设备挂多个非终态工单。
修复两处:
1. DispatchEngineImpl.autoDispatchNext 入口加设备空闲校验:
若执行人名下还有 DISPATCHED/CONFIRMED/ARRIVED/PAUSED 工单(排除
completedOrderId),直接早返回,不再派发。所有调用方(保洁/安保
handleCancelled、asyncCompleteAndDispatchNext、xxl-job 空闲扫描)
自动受保护。新增 OpsOrderMapper.selectActiveByAssignee。
2. BadgeDeviceStatusEventListener.handleDispatched 移除"残留取消":
旧逻辑用 REQUIRES_NEW 事务 + 吞异常,是对数据已错乱场景的暴力兜底,
失败时导致误杀。改为只打 ERROR 告警暴露问题,仅清理 Redis 关联。
真正的防线在 DispatchEngine 入口。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 10:54:54 +08:00 |
|
|
|
a2f500fa20
|
feat(tenant): 租户-项目两级架构 Phase 2 — IoT + Ops 业务迁移
DO 迁移 (15个 TenantBaseDO → ProjectBaseDO):
- IoT: IotDeviceDO
- Ops 核心: OpsOrderDO, OpsOrderEventDO, OpsOrderDispatchDO, OpsOrderQueueDO,
OpsBusAreaDO, OpsAreaDeviceRelationDO, OpsDeviceTrajectoryDO
- Ops 保洁: OpsOrderCleanExtDO, OpsCleanerStatusDO, OpsCleanerPerformanceMonthlyDO,
OpsInspectionRecordDO, OpsInspectionRecordItemDO
- Ops 安保: OpsOrderSecurityExtDO, OpsAreaSecurityUserDO
IoT 适配:
- IotDeviceRespDTO 新增 projectId 字段
- IotDeviceMessage 新增 projectId 字段
- IotDeviceMessageServiceImpl.appendDeviceMessage() 设置 projectId
- IotCleanRuleMessageHandler 嵌套 ProjectUtils.execute() 设置项目上下文
缓存改造:
- ProjectRedisCacheManager extends TenantRedisCacheManager,追加 :projectId 后缀
- ViewshTenantAutoConfiguration 替换为 ProjectRedisCacheManager
SQL 迁移脚本 (sql/mysql/project/):
- 01-create-tables.sql: system_project + system_user_project 建表
- 02-default-data.sql: 默认项目 + 用户关联回填
- 03-alter-business-tables.sql: 15 张表添加 project_id (NULL → 回填 → NOT NULL → 索引)
- 04-index-audit.sql: 现有索引审计 + project_id 补充建议
- 99-rollback.sql: 完整回滚方案
附带修复:
- fix(ops): UserDispatchStatusServiceImpl 添加缺失的 KEY_PREFIX 常量
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-16 22:27:34 +08:00 |
|
|
|
73e67dd3ec
|
Merge branch 'master' into feat/multi-tenant
Resolve conflicts by accepting master changes for:
- Jenkinsfile (CI/CD release/next branch support)
- OrderCodeGenerator (Redis seq sync fix)
- OrderCodeGeneratorTest (updated tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-16 18:08:14 +08:00 |
|
|
|
6bbd49355d
|
fix(ops): 修复工单编号生成器 Redis 序号与数据库不同步导致的重复编号问题
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
问题:Redis 重启或 key 过期后序号从 1 重新计数,与数据库已有编号冲突。
修复方案:
- 应用启动后首次生成时,从数据库查询当天最大序号校准 Redis
- 使用 Lua 脚本原子操作(校准 + 自增),避免并发竞态
- 后续调用走纯 Redis INCR,无额外数据库开销
- SQL 使用 deleted = b'0' 兼容 bit(1) 列类型
- LIKE 查询转义 % 和 _ 通配符
- 校准异常向上抛出,避免静默产生重复
- calibratedKeys 跨天自动清理旧条目
同步更新单元测试,覆盖校准、纯 Redis 自增、异常处理、SQL 转义等 13 个用例。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-13 23:19:20 +08:00 |
|
|
|
705717a5b1
|
Merge branch 'master' into feat/multi-tenant
# Conflicts:
# viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/dal/redis/TrafficActiveOrderRedisDAO.java
# viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java
# viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/dispatch/UserDispatchStatusServiceImpl.java
|
2026-04-13 14:35:27 +08:00 |
|
|
|
b379fc6741
|
feat(ops): timeline 接口 deviceId 改为可选,支持全设备查询
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
不传 deviceId 时查询该日期所有设备的轨迹记录,复用
selectByDateAndDevice 的 LIMIT 5000 安全上限。
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-05 15:27:05 +08:00 |
|
|
|
9ffaac5c91
|
feat(ops): 新增轨迹统计接口 summary/hourly-trend/area-stay-stats
- summary: KPI 卡片(作业时长、覆盖区域数、事件数、平均停留)
- hourly-trend: 按小时聚合出入趋势
- area-stay-stats: 区域停留分布(含 fullAreaName,按时长降序)
- deviceId 可选,不传则汇总全部设备
- selectByDateAndDevice 加 LIMIT 5000 安全上限
- 删除无调用方的 selectTimeline 方法
- enrichWithAreaInfo 改用 buildPaths 批量构建路径
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-05 15:26:14 +08:00 |
|
|
|
368fa90156
|
refactor(ops): 轨迹区域展示改用 fullAreaName 替代 buildingName/floorNo
TrajectoryRespDTO 移除 buildingName、floorNo 字段,新增 fullAreaName
(完整路径如"A园区/A栋/3层/男卫")。AreaPathBuilder 新增 buildPaths
批量方法,一次查询所有父级区域避免 N+1;正则预编译为静态常量。
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-05 15:25:47 +08:00 |
|
|
|
9780d6c3f7
|
fix(ops): 区域设备 RPC 接口添加 @TenantIgnore 解决定时任务调用时租户上下文缺失
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
IoT 模块 BeaconRegistryServiceImpl 每30分钟通过 Feign 调用 /beacons/all 接口,
因定时任务无租户上下文导致 TenantContextHolder NPE。对跨租户查询的方法添加
@TenantIgnore 注解忽略多租户过滤。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-01 09:23:27 +08:00 |
|
|
|
306303ab16
|
fix(ops): 启动时校准人员调度状态
- 为 UserDispatchStatusService 增加基于 DB 的重建能力\n- 扫描 Redis 中的人员调度 key,按实际活跃工单数修正 status、activeOrderCount、waitingTaskCount\n- 新增启动初始化器,服务启动时自动执行一次校准,缓解事件丢失导致的 BUSY 残留
|
2026-03-31 22:58:09 +08:00 |
|
|
|
d3eecc63ef
|
feat(trajectory): 新增轨迹后台查询与实时位置接口
- 新增轨迹分页、时间线、统计摘要等查询 DTO\n- 提供轨迹后台控制器,支持工牌下拉、轨迹查询、实时位置查询\n- 接入 TrajectoryStateApi 的 Feign 配置,打通 Ops 对 IoT 实时位置状态的读取
|
2026-03-31 22:56:49 +08:00 |
|
|
|
bf5aa21648
|
feat(trajectory): 新增轨迹事件消费与落库模型
- 新增 ops_device_trajectory 表及轨迹数据对象、Mapper\n- 消费 trajectory-enter / trajectory-leave 事件并做幂等处理\n- 落地设备进入/离开区域记录,补充停留时长与离开原因字段\n- 在服务层封装轨迹写入、关闭未离场记录等核心逻辑
|
2026-03-31 22:56:18 +08:00 |
|
|
|
11dcb57ff3
|
feat(trajectory): 新增轨迹检测与 Beacon 注册表
|
2026-03-31 22:53:06 +08:00 |
|
|
|
19cb25b0ea
|
refactor(ops): 15 个 DO 基类从 BaseDO 规范化为 TenantBaseDO
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
数据库表已有 tenant_id 列,拦截器实际可正常工作,但 DO 继承
BaseDO 语义不明确。统一改为 TenantBaseDO 以明确租户隔离语义。
ops-biz: OpsOrderDO, OpsOrderDispatchDO, OpsOrderEventDO,
OpsOrderQueueDO, OpsBusAreaDO, OpsAreaDeviceRelationDO,
OpsBusinessEventLogDO
environment-biz: OpsOrderCleanExtDO, OpsInspectionRecordDO,
OpsInspectionTemplateDO, OpsInspectionRecordItemDO,
OpsCleanerStatusDO, OpsCleanerPerformanceMonthlyDO
security-biz: OpsOrderSecurityExtDO, OpsAreaSecurityUserDO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-30 11:42:09 +08:00 |
|
|
|
74f6207843
|
fix(ops): XML 手写 SQL 添加 tenant_id 预编译参数过滤
yudao 官方明确 MyBatis Plus 拦截器不处理 XML 手写 SQL,需手动过滤。
将 ${} OGNL 表达式改为 #{tenantId} 预编译参数,避免 NPE 和
@TenantIgnore 不兼容问题。
- OpsOrderMapper: 8 条统计 SQL 添加 AND tenant_id = #{tenantId}
- OpsTrafficStatisticsMapper: deleteByStatHourBefore 补上 tenant_id
- OpsStatisticsServiceImpl: 10 处调用传入 tenantId 参数
- TrafficStatisticsCleanupJob: executeIgnore → @TenantJob + 显式传参
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-30 11:40:39 +08:00 |
|
|
|
df2d14ce26
|
feat(ops): 新增 OpsRedisKeyBuilder 统一管理 Redis Key 租户隔离
新建 OpsRedisKeyBuilder 集中式工具类,所有 Ops 模块 Redis Key 统一使用
:t{tenantId} 格式实现多租户隔离。迁移以下服务的 Key 构建:
- RedisOrderQueueServiceImpl(派单队列/信息/锁)
- UserDispatchStatusServiceImpl(调度状态)
- BadgeDeviceStatusServiceImpl(工牌状态)
- TrafficActiveOrderRedisDAO(客流活跃工单)
- TtsQueueConsumer(TTS 队列/锁/循环)
- OrderCodeGenerator(工单编码序号)
- AreaDeviceServiceImpl(区域设备配置缓存)
- TrafficStatisticsPersistJob(持久化锁)
- BadgeDeviceStatusRedisDAO(IoT 侧工牌状态)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-30 11:36:18 +08:00 |
|
|
|
a9941a29a9
|
fix(ops): 状态机允许 CONFIRMED → COMPLETED,支持安保确认后直接完单
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
安保工单不需要信标到岗检测(ARRIVED),确认接单后可直接提交处理结果完成。
原规则 CONFIRMED → {ARRIVED, CANCELLED} 缺少 COMPLETED,导致安保人员完单报错:
"非法状态转换: CONFIRMED -> COMPLETED"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 17:11:49 +08:00 |
|
|
|
55ef659364
|
feat(ops): 手动派单遵循执行人忙碌状态,忙碌时入队等待
ManualOrderActionFacade.dispatch:
- 新增 strategy.isAssigneeIdle() 判断,空闲→DISPATCHED,忙碌→QUEUED
- 不再无条件直接派发
OrderBusinessStrategy:
- 新增 isAssigneeIdle() 默认方法,默认返回 true
CleanOrderBusinessStrategy:
- isAssigneeIdle 通过 BadgeDeviceStatusService.isBusy() 判断设备忙碌
SecurityOrderBusinessStrategy:
- isAssigneeIdle 通过 UserDispatchStatusService.isIdle() 判断人员忙碌
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 16:37:30 +08:00 |
|
|
|
78396cf35a
|
fix(ops): 调度状态补偿 QUEUED→终态跳过 DISPATCHED 场景
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
UserDispatchStatusEventListener:
- assigneeId 兜底从工单主表获取(forceComplete 等 payload 缺失场景)
- QUEUED→COMPLETED/CANCELLED 补偿 decrementWaitingTaskCount
UserDispatchStatusServiceImpl:
- 新增 LUA_DECREMENT_WAITING 脚本,安全递减 waitingTaskCount(不低于 0)
OpsOrderEventService:新增 8 参数 recordEvent 重载(含 operatorName)
DispatchEngineServiceAdapter:reason 文案统一为"手动派单"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 16:09:30 +08:00 |
|
|
|
6e5366be57
|
feat(ops): OrderTransitionRequest 新增 operatorName,EventPublishHandler 透传到事件 payload
手动操作场景下 operatorName 沿 facade → request → EventPublishHandler → payload 透传,
listener 从 payload 直接读取,不再重复调用 AdminUserApi。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 16:07:34 +08:00 |
|
|
|
6b01c29cb1
|
feat(ops): 新增统一手动动作门面与审计服务
ManualOrderActionFacade:
- 统一骨架:查单校验 → 条线前置 → 状态变更 → 条线后置
- dispatch 支持 PENDING/QUEUED 状态,用独立对象更新 assignee 避免覆盖状态机已写入的 status
- cancel/complete 走 transition() 透传 operatorName 到领域事件
- dispatch/cancel/complete 业务日志由 listener 统一记录,不重复写
OrderAuditService:
- 仅用于 create/upgradePriority(不经过状态机的手动动作)
- 只写 ops_business_event_log,不写 ops_order_event(时间轴只由状态机写)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 16:07:13 +08:00 |
|
|
|
e1d967a65e
|
feat(ops): 新增手动操作枚举与模型定义
引入统一手动动作基础设施:
- ManualActionTypeEnum: 手动动作类型(创建/派单/取消/完单/升级)
- OrderActionSourceEnum: 动作来源(管理后台/开放接口)
- OrderAuditPayloadKeys: 审计 payload 标准化 key
- OrderEventTypeEnum: 事件类型枚举值对齐状态机(DISPATCHED/QUEUED/CONFIRMED)
- OperatorContext: 统一操作人上下文
- *Command: 手动动作命令模型(Dispatch/Cancel/Complete/UpgradePriority)
- OrderBusinessStrategy: 条线策略接口
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-27 15:53:20 +08:00 |
|
|
|
4763caf2cc
|
fix(ops): 移除 OrderLifecycleManager 中重复的业务日志记录
LifecycleManager 是通用层,业务日志统一由各业务线 EventListener 负责。
移除 cancelOrder、completeOrder(ADMIN)、pauseOrder、resumeOrder 中的
eventLogRecorder 调用,避免与 SecurityOrderEventListener 等监听器产生
重复日志记录。保留 interruptOrder 的日志(P0 打断是通用机制)。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-26 16:09:58 +08:00 |
|
|
|
bde680c188
|
fix(ops): VspNotifyClient 强制 HTTP/1.1,修复 h2c 升级导致 body 丢失
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
JDK 17 HttpClient 默认尝试 HTTP/2 升级(h2c),uvicorn 不支持 h2c
协商,处理 Upgrade 请求时丢弃 body,导致 Python 端收到空 {}。
强制 HTTP_1_1 禁用 h2c 升级。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-26 16:04:32 +08:00 |
|
|
|
93bc1f10c3
|
fix(security): 规范安保工单事件日志,消除重复记录
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
修复前同一次派发产生 4 条 ORDER_DISPATCHED 日志的问题:
- 新增 ORDER_QUEUED 枚举,handleQueued 改用正确的 event_type
- handleArrived 改用 ORDER_ARRIVED(之前错用 ORDER_CONFIRM)
- 移除 onOrderCreated 中派单成功的重复日志(由状态监听统一记录)
- 移除 DispatchEngineImpl.dispatch 上的 @BusinessLog 注解(避免与
业务层 EventListener 日志重复)
- ORDER_DISPATCHED 描述改为"工单派发",ORDER_ARRIVED 改为"人员到达"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-26 15:30:42 +08:00 |
|
|
|
bd70f3bc8a
|
fix(ops): 修复队列脏数据导致工单永远排队无法派发
问题:autoCompleteOrder 直接调用 orderStateMachine.forceTransition,
绕过责任链导致队列记录残留 WAITING,autoDispatchNext 反复命中脏数据
失败,人员状态永远 BUSY,新工单全部 ENQUEUE_ONLY 形成死循环。
修复:
1. SecurityOrderServiceImpl 所有状态操作统一走 OrderLifecycleManager,
移除对 OrderStateMachine 的直接依赖
2. autoDispatchNext 增加循环遍历 + 工单状态校验,跳过并清理非 QUEUED
的脏队列记录,增加 maxSkip=50 防护上限
3. forceComplete 返回值校验,失败时抛异常而非静默继续
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-26 15:30:02 +08:00 |
|
|
|
8406a80655
|
feat(ops): OrderLifecycleManager 支持 forceComplete 强制完成工单
新增 forced 字段到 OrderTransitionRequest,StateTransitionHandler 根据
该字段选择 transition 或 forceTransition,确保强制状态转换也走完整
责任链(队列同步 + 事件发布),避免绕过 QueueSyncHandler 产生脏数据。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-26 15:29:29 +08:00 |
|
|
|
9115e03878
|
feat(ops): 新增通用人员调度状态服务 UserDispatchStatusService
基于 Redis Hash 维护人员维度的调度状态,供安保/工程/客服业务线共用。
与保洁的 BadgeDeviceStatusService(设备维度)并行。
核心设计:
- Redis Key: ops:user:dispatch:{userId},存储 status/activeOrderCount/waitingTaskCount 等
- 所有写操作使用 Lua 脚本原子执行,保证多业务线并发安全
- 事件监听器 @EventListener(事务内同步)自动排除 CLEAN 类型
- Redis 丢数据时降级为 IDLE,下一次事件自动重建(自愈)
新增文件:
- UserDispatchStatusDTO (ops-api)
- UserDispatchStatusService 接口 (ops-biz)
- UserDispatchStatusServiceImpl - Lua 脚本实现 (ops-biz)
- UserDispatchStatusEventListener - 通用事件监听 (ops-biz)
- UserAssigneeStatusAdapter - AssigneeStatus 适配器 (ops-biz)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-25 15:44:42 +08:00 |
|
|
|
718d14e162
|
feat(ops): 派单链路全程传递 assigneeName 和 assigneePhone
AssigneeRecommendation、OrderDispatchContext、OrderTransitionRequest
新增 assigneePhone 字段;DispatchEngineImpl 在三条派单路径中传递
phone;EventPublishHandler 将 assigneeName 和 assigneePhone 写入
事件 payload,修复下游监听器取不到 assigneeName 的问题。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-25 14:26:40 +08:00 |
|
|
|
e44c1f6f4e
|
feat(ops): 新增 VSP 企微通知网关基础设施
- VspNotifyClient/Impl: HTTP 客户端,支持网络异常重试 + 线性退避
- VspNotifyProperties/Config: 配置属性与 RestTemplate Bean(JdkClientHttpRequestFactory)
- VspSendCardReqDTO/VspSyncStatusReqDTO/VspResponseDTO: 请求响应 DTO
- WechatUserIdResolver: 企微 userId 查询工具,通过 SocialUserApi 查询,供各业务线复用
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-25 11:28:53 +08:00 |
|
|
|
47c768ec6f
|
feat(ops): 整改工单去重 — 已有活跃工单时升级优先级而非重复创建
- InspectionRectificationServiceImpl 先查区域活跃保洁工单:
排队中(PENDING/QUEUED)→升级一级优先级;已派发/已到达→静默跳过
- OpsOrderMapper 新增 selectActiveCleanOrder,使用枚举替代硬编码终态
- InspectionAsyncHandler 清理归属判定注释代码,替换为 TODO 标记
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-22 15:00:25 +08:00 |
|
|
|
f213510b03
|
refactor(ops): 巡检接口重构 — 位置校验前移、列表/详情分离、表单聚合
接口变更:
- 删除 POST /verify-location,位置校验改为前端本地蓝牙信标匹配
- 新增 GET /record/get 巡检记录详情(含明细项、照片)
- GET /list-by-area 升级为返回完整巡检表单(区域+检查项+信标配置)
- GET /record/page 返回类型改为 VO,新增区域全路径名称和巡检员姓名
- 提交和表单接口增加 inspector 角色校验
代码质量(Code Review 修复):
- 提取 buildAreaFullName 至 OpsBusAreaMapper 消除两个 Service 的重复
- 新增 buildAreaFullNameMap 批量方法,修复分页场景 N+1 查询
- getRecordDetail 中 adminUserApi 改用 getUserMap + try-catch 降级
- InspectionTemplateServiceImpl 去掉 ObjectMapper 依赖,直接 Map 取值
- RSSI 阈值取最宽松值逻辑添加语义注释
- 巡检错误码从 1-020-003 迁移至 1-020-004,修复与安保模块的码段冲突
- InspectionRecordDetailRespVO.photos 使用 @OssPresignUrl 自动预签名
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-22 15:00:07 +08:00 |
|
|
|
55e9003142
|
refactor(ops): 迁移 ops-biz 公共层 DTO 至 service/order/dto 包
将 11 个工单 DTO 从 dal.dataobject.dto 包迁移至 service.order.dto 包,
DTO 属于业务契约而非数据库实体,放在 service 层更符合分层规范。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-19 09:31:59 +08:00 |
|
|
|
e8a8baf62f
|
feat(ops): 状态机新增 forceTransition 强制跳转方法
适用于系统自动结单等场景,允许跳过转换规则直接跳转到终态,
但仍校验终态不可再转换,且完整记录事件流。
重构:抽取 doTransition 公共方法,transition 和 forceTransition
通过 validate 参数区分,消除重复代码。新增 TERMINAL_STATES
显式终态集合替代隐式空 Set 判断。
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-03-18 22:25:17 +08:00 |
|
|
|
825c8eecca
|
refactor(ops): 提取 AreaPathBuilder 公共组件,消除保洁/安保 buildAreaPath 重复代码
将 CleanOrderServiceImpl 中的 buildAreaPath 私有方法提取到 ops-biz 公共层
AreaPathBuilder 组件,供各业务模块(保洁、安保等)共享使用。同时优化:
- 用正则 matches("\d+") 替代 try-catch NumberFormatException 做数字校验
- 增加相邻重复ID去重保护
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-03-15 10:30:03 +08:00 |
|
|
|
5f804605c7
|
refactor(ops): 收口散落的 eventType 硬编码为 LogType 枚举引用
替换 CleanOrderCreateEventHandler、OrderLifecycleManagerImpl、
DispatchEngineImpl 中的字符串常量为 LogType.XXX.getCode(),
同时将 DispatchEngine 的 @BusinessLog description 改为"工单自动派发"。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-03-11 17:34:30 +08:00 |
|
|
|
dc75c78d16
|
fix(ops): 修复业务日志 title 显示英文常量,改为中文映射
/business-logs 接口 title 回退取 eventType 时,通过 LogType.getByCode()
映射中文 description 作为标题。同步调整 @BusinessLog 注解 type 属性
改用 LogType 枚举。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-03-11 17:34:19 +08:00 |
|
|
|
fc9393e723
|
refactor(ops): 扩展 LogType 枚举,补全工单生命周期与 IoT 审计事件类型
新增 ORDER_CREATED/CONFIRM/ARRIVED/COMPLETED 等工单生命周期枚举、
BEACON_ARRIVE_CONFIRMED/BEACON_COMPLETE_REQUESTED 等 IoT 审计枚举,
添加 getByCode() 反查方法支持中文 title 映射。
同步新增 LogModule 常量类收口模块标识。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-03-11 17:34:08 +08:00 |
|
|
|
b8d0a77156
|
feat(ops): 调整队列评分权重,楼层差×3 老化÷1.67,临界值 1 层=20 分钟
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
FLOOR_WEIGHT 20→60、AGING_WEIGHT 5→3,强化就近派单效果。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-03-09 15:59:11 +08:00 |
|