OpService.java 4.2 KB
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<String, Object> get(String sId) {
        List<Map<String, Object>> r = jdbc.queryForList("SELECT * FROM ai_op_queue WHERE sId=?", sId);
        return r.isEmpty() ? null : r.get(0);
    }

    /** 会话里最近一条待确认(draft)操作。 */
    public Map<String, Object> pending(String convId) {
        List<Map<String, Object>> 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);
    }
}