package com.xly.service; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; /** * ai_op_queue 暂存管理 —— 写操作的人在环闭环:ProposeWrite 写 draft,用户确认后 confirm 端点执行。 */ @Service public class OpService { private final JdbcTemplate jdbc; private final AuditService audit; public OpService(JdbcTemplate jdbc, AuditService audit) { this.jdbc = jdbc; this.audit = audit; } /** 暂存一条 draft 写操作,返回 opId(不执行)。 */ public String createDraft(String userId, String opType, String formId, String moduleId, String table, String billId, String field, String fieldLabel, String oldValue, String newValue, String description) { String sId = "op-" + System.currentTimeMillis() + "-" + Integer.toHexString((int) (Math.random() * 0xFFFFF)); jdbc.update( "INSERT INTO ai_op_queue(sId,sUserId,sOpType,sTargetFormId,sTargetModuleId,sTargetTable," + "sTargetBillId,sField,sFieldLabel,sOldValue,sNewValue,sDescription,sStatus,tCreateDate) " + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?, 'draft', NOW())", sId, userId, opType, formId, moduleId, table, billId, field, fieldLabel, oldValue, newValue, description); audit.log(userId, null, "propose", table + "#" + billId, description, true, "draft staged (" + sId + ")"); return sId; } /** 暂存一条 draft(payload 版,用于 create:sPayload=列->值 JSON)。 */ public String createDraftPayload(String userId, String opType, String formId, String moduleId, String table, String payload, String description) { String sId = "op-" + System.currentTimeMillis() + "-" + Integer.toHexString((int) (Math.random() * 0xFFFFF)); jdbc.update( "INSERT INTO ai_op_queue(sId,sUserId,sOpType,sTargetFormId,sTargetModuleId,sTargetTable," + "sPayload,sDescription,sStatus,tCreateDate) VALUES(?,?,?,?,?,?,?,?, 'draft', NOW())", sId, userId, opType, formId, moduleId, table, payload, description); audit.log(userId, null, "propose", table + " (create)", description, true, "draft staged (" + sId + ")"); return sId; } /** 把 draft 关联到会话(控制器在工具执行后调用;控制器持有 conversationId)。 */ public void attachConversation(String opId, String convId) { jdbc.update("UPDATE ai_op_queue SET sConversationId=? WHERE sId=?", convId, opId); } public Map get(String sId) { List> r = jdbc.queryForList("SELECT * FROM ai_op_queue WHERE sId=?", sId); return r.isEmpty() ? null : r.get(0); } /** 会话里最近一条待确认(draft)操作。 */ public Map pending(String convId) { List> r = jdbc.queryForList( "SELECT * FROM ai_op_queue WHERE sConversationId=? AND sStatus='draft' ORDER BY tCreateDate DESC LIMIT 1", convId); return r.isEmpty() ? null : r.get(0); } public void setStatus(String sId, String status, String resultMsg) { jdbc.update("UPDATE ai_op_queue SET sStatus=?, sResultMsg=?, tConfirmDate=NOW() WHERE sId=?", status, resultMsg, sId); } /** * 抢占一条 draft(CAS):只有把 draft 改成 executing 的那个请求返回 true,并发/重复确认直接落空。 * 没有它,两个并发 confirm 都能通过「读到 draft」的判断,把同一张单执行两次。 */ public boolean claim(String sId) { return jdbc.update("UPDATE ai_op_queue SET sStatus='executing', tConfirmDate=NOW() " + "WHERE sId=? AND sStatus='draft'", sId) == 1; } /** 执行失败/异常时把 executing 退回 draft 之外的终态由 setStatus 负责;这里仅用于放弃抢占。 */ public void release(String sId) { jdbc.update("UPDATE ai_op_queue SET sStatus='draft' WHERE sId=? AND sStatus='executing'", sId); } }