工单不丢、状态可追溯:Agent 工单的持久化与生命周期管理

40 阅读4分钟

上一篇文章工单创建了,但存在 ConcurrentHashMap 里——服务一重启,所有工单消失。这篇文章把工单落到数据库,加上状态流转(待处理→处理中→已完成),让 Agent 的工单真正「留下来、管起来」。


问题:内存工单的致命缺陷

上一版的 WorkOrderToolConcurrentHashMap 存工单:

private final ConcurrentHashMap<String, WorkOrder> orders = new ConcurrentHashMap<>();

三个问题:

  1. 重启丢失——服务重启后所有工单消失
  2. 不可查询历史——不能查「上月有多少工单」
  3. 没有状态流转——工单创建后永远是 PENDING,无法标记完成

产线上工单需要的是:持久存储 + 状态流转 + 可审计


方案:嵌入式 H2 + 工单生命周期

不引入 MySQL/PostgreSQL——用一个嵌入式 H2 数据库,零配置,随应用启停。

工单生命周期:

PENDING(待处理)→ IN_PROGRESS(处理中)→ COMPLETED(已完成)→ CLOSED(已关闭)
                        ↓
                    CANCELLED(已取消)

只有两个方向:正常流转(→)和取消(↓)。


实现

1. 数据库表

CREATE TABLE work_orders (
    id          VARCHAR(20) PRIMARY KEY,
    device_id   VARCHAR(64)  NOT NULL,
    type        VARCHAR(20)  NOT NULL,   -- 维修/检查/保养
    priority    VARCHAR(10)  NOT NULL,   -- HIGH/MEDIUM/LOW
    description TEXT,
    assignee    VARCHAR(64)  DEFAULT '值班工程师',
    status      VARCHAR(20)  DEFAULT 'PENDING',
    created_at  TIMESTAMP    DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP    DEFAULT CURRENT_TIMESTAMP,
    completed_at TIMESTAMP,
    closed_at   TIMESTAMP
);

2. WorkOrderTool 改造成持久化版本

@Component
public class WorkOrderTool {

    private final JdbcTemplate jdbc;

    @Tool("创建维修工单。仅当确认设备存在硬件故障需要人工介入时使用。")
    public String createWorkOrder(
            @P("设备ID") String deviceId,
            @P("工单类型:维修/检查/保养") String type,
            @P("优先级:HIGH/MEDIUM/LOW") String priority,
            @P("故障描述和诊断结论") String description) {

        String id = "WO-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
        jdbc.update(
            "INSERT INTO work_orders (id, device_id, type, priority, description) VALUES (?,?,?,?,?)",
            id, deviceId, type, priority, description
        );
        return findById(id).toString();  // 返回完整工单 JSON
    }

    @Tool("将工单状态更新为 IN_PROGRESS(开始处理)")
    public String startWorkOrder(@P("工单编号") String workOrderId) {
        jdbc.update("UPDATE work_orders SET status='IN_PROGRESS', updated_at=NOW() WHERE id=?",
                workOrderId);
        return "工单 " + workOrderId + " 已开始处理";
    }

    @Tool("将工单状态更新为 COMPLETED(已完成)")
    public String completeWorkOrder(@P("工单编号") String workOrderId) {
        jdbc.update(
            "UPDATE work_orders SET status='COMPLETED', completed_at=NOW(), updated_at=NOW() WHERE id=?",
            workOrderId);
        return "工单 " + workOrderId + " 已标记完成";
    }

    public WorkOrder findById(String id) {
        return jdbc.queryForObject(
            "SELECT * FROM work_orders WHERE id=?",
            new BeanPropertyRowMapper<>(WorkOrder.class), id
        );
    }

    public List<WorkOrder> findByDevice(String deviceId) {
        return jdbc.query(
            "SELECT * FROM work_orders WHERE device_id=? ORDER BY created_at DESC",
            new BeanPropertyRowMapper<>(WorkOrder.class), deviceId
        );
    }
}

3. 新增两个 Tool 方法注册到 Agent

startWorkOrdercompleteWorkOrder 作为 @Tool 注册到 DeviceAgent,LLM 可以在对话中直接更新工单状态:

用户:「CNC-001 的维修完成了,关闭工单」
Agent:调用 completeWorkOrder("WO-4BCECB3A") → 工单状态 → COMPLETED

4. 新增 API 端点

// 查询设备的所有历史工单
@GetMapping("/workorder/device/{deviceId}")
public ResponseEntity<List<WorkOrder>> listByDevice(@PathVariable String deviceId)

// 工单统计
@GetMapping("/workorder/stats")
public ResponseEntity<Map<String, Object>> stats()
// 返回:{ total: 15, pending: 3, inProgress: 5, completed: 7 }

状态流转规则

当前状态可流转到说明
PENDINGIN_PROGRESS工程师接单
PENDINGCANCELLED误报或无需处理
IN_PROGRESSCOMPLETED维修完成
IN_PROGRESSCANCELLED维修无法执行
COMPLETEDCLOSED管理员审核关闭

状态变更由 updated_at 自动记录,completed_at / closed_at 单独保留时间戳,方便后续统计分析。


端到端效果

# 1. Agent 诊断并创建工单
curl -X POST http://localhost:8080/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"CNC-001 振动异常,诊断并创建工单"}'
# → WO-8F3A2B1C 已创建,状态 PENDING

# 2. 工程师开始处理
curl -X POST http://localhost:8080/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"我接单了 WO-8F3A2B1C,开始维修"}'
# → 工单状态 → IN_PROGRESS

# 3. 维修完成
curl -X POST http://localhost:8080/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"WO-8F3A2B1C 已修好,关闭工单"}'
# → 工单状态 → COMPLETED

# 4. 查询 CNC-001 的历史工单
curl http://localhost:8080/api/agent/workorder/device/CNC-001
# → 3 条工单记录,含状态和时间戳

为什么不用 MySQL/PostgreSQL?

H2 的选择理由:

  • 零配置——不需要额外的 Docker 容器,pom.xml 加一个依赖即可
  • 嵌入式——随 Java 进程启停,本地开发无额外负担
  • 兼容性好——标准 SQL,后续迁移到 MySQL/PostgreSQL 只需改连接串
  • 够用——工单量不大(日几十条),H2 完全胜任

如果生产环境已经是 Spring Boot + MySQL,把 H2 换成 MySQL 只需要改 application.yml 中的一行 URL。


代码结构变化

agent/tools/
├── WorkOrderTool.java     — 改造:HashMap → JdbcTemplate,新增状态流转方法

agent/model/
└── WorkOrder.java         — 新增 updated_at / completed_at / closed_at 字段

controller/
└── AgentController.java   — 新增 /workorder/device/{id} 和 /workorder/stats

resources/
├── application.yml         — 新增 H2 数据源配置
└── schema.sql              — 工单表 DDL(Spring Boot 自动执行)

下一步

工单持久化后,整套系统闭环了——设备数据采集(MQTT→TDEngine)→ Agent 诊断(RAG + CoT)→ 工单生成(WorkOrderTool)→ 工单管理(持久化 + 状态流转)。

下一篇文章从整体回顾这个系统的架构设计,总结「工业 Agent 的六大设计原则」。


代码仓库:github.com/LaoLiang-ag…


本文由 LaoLiang 原创,首发于掘金/知乎/微信公众号。转载请联系作者。