Forráskód Böngészése

对接海康设备,添加完整消费逻辑

xiari 11 hónapja
szülő
commit
43b76bd725
15 módosított fájl, 954 hozzáadás és 47 törlés
  1. 1 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/CheckBusiness.java
  2. 12 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java
  3. 106 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/XfFailedRecordController.java
  4. 88 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/XfFailedRecord.java
  5. 89 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/bo/XfFailedRecordBo.java
  6. 96 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/vo/XfFailedRecordVo.java
  7. 2 20
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/ConsumptionEventHandler.java
  8. 101 23
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/TransactionRecordEventHandler.java
  9. 99 3
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/timedtask/HandleTask.java
  10. 15 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/mapper/XfFailedRecordMapper.java
  11. 77 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/IXfFailedRecordService.java
  12. 181 0
      ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/XfFailedRecordServiceImpl.java
  13. 25 0
      ruoyi-server/ruoyi-server-hik/src/main/resources/mapper/FailedRecord/XfFailedRecordMapper.xml
  14. 41 0
      sql/2025-6-9/kingbase/ddl.sql
  15. 21 0
      sql/2025-6-9/mysql/ddl.sql

+ 1 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/CheckBusiness.java

@@ -301,7 +301,7 @@ public class CheckBusiness {
     public R<ErrorInfo> checkBill(ConsumptionBo bo, RemoteUserAccountVo userAccountVo, RemoteCardVo userCardVo,
                                   XfTermVo useTermVo, List<PtBagVo> bagVoList, RemoteMealTypeVo mealTypeVo,
                                   RemoteOperatorVo operatorVo) {
-        R<ErrorInfo> result = checkCardNo(bo, userAccountVo, userCardVo);
+        R<ErrorInfo> result = checkUser(bo, userAccountVo, userCardVo);
         if (R.isError(result)) {
             return result;
         }

+ 12 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/TestController.java

@@ -21,6 +21,7 @@ import org.dromara.server.hik.domain.dto.QueryDto;
 import org.dromara.server.hik.domain.dto.UploadEmpDto;
 import org.dromara.server.hik.event.EventHandleRouter;
 import org.dromara.server.hik.event.domain.FileContent;
+import org.dromara.server.hik.event.timedtask.HandleTask;
 import org.dromara.server.hik.service.ISendDeviceService;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -59,6 +60,8 @@ public class TestController {
 
     private final EventHandleRouter eventHandleRouter;
 
+    private final HandleTask handleTask;
+
     @DubboReference
     private final RemoteConsumeService remoteConsumeService;
 
@@ -466,4 +469,13 @@ public class TestController {
         }
         return null;
     }
+
+    /**
+     * 手动执行定时任务
+     */
+    @PostMapping("/executeTask")
+    public void executeTask() {
+        handleTask.handleTransactionRecordEventTask();
+    }
+
 }

+ 106 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/controller/XfFailedRecordController.java

@@ -0,0 +1,106 @@
+package org.dromara.server.hik.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
+import org.dromara.server.hik.service.IXfFailedRecordService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 海康消费失败记录
+ * 前端访问路由地址为:/FailedRecord/xfFailedRecord
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/FailedRecord/xfFailedRecord")
+public class XfFailedRecordController extends BaseController {
+
+    private final IXfFailedRecordService xfFailedRecordService;
+
+    /**
+     * 查询海康消费失败记录列表
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:list")
+    @GetMapping("/list")
+    public TableDataInfo<XfFailedRecordVo> list(XfFailedRecordBo bo, PageQuery pageQuery) {
+        return xfFailedRecordService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出海康消费失败记录列表
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:export")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(XfFailedRecordBo bo, HttpServletResponse response) {
+        List<XfFailedRecordVo> list = xfFailedRecordService.queryList(bo);
+        ExcelUtil.exportExcel(list, "海康消费失败记录", XfFailedRecordVo.class, response);
+    }
+
+    /**
+     * 获取海康消费失败记录详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:query")
+    @GetMapping("/{id}")
+    public R<XfFailedRecordVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable String id) {
+        return R.ok(xfFailedRecordService.queryById(id));
+    }
+
+    /**
+     * 新增海康消费失败记录
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:add")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody XfFailedRecordBo bo) {
+        return toAjax(xfFailedRecordService.insertByBo(bo));
+    }
+
+    /**
+     * 修改海康消费失败记录
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:edit")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfFailedRecordBo bo) {
+        return toAjax(xfFailedRecordService.updateByBo(bo));
+    }
+
+    /**
+     * 删除海康消费失败记录
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:remove")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable String[] ids) {
+        return toAjax(xfFailedRecordService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 88 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/XfFailedRecord.java

@@ -0,0 +1,88 @@
+package org.dromara.server.hik.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 海康消费失败记录对象 t_xf_failed_record
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_xf_failed_record")
+public class XfFailedRecord extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id")
+    private String id;
+
+    /**
+     * 用户流水号
+     */
+    private String userNo;
+
+    /**
+     * 物理卡号
+     */
+    private String factoryId;
+
+    /**
+     * 消费时间
+     */
+    private Date consumeDate;
+
+    /**
+     * 设备记录流水号
+     */
+    private String termRecordId;
+
+    /**
+     * 消费金额
+     */
+    private String consumeMoney;
+
+    /**
+     * 设备mac地址
+     */
+    private String termMac;
+
+    /**
+     * 设备机号
+     */
+    private String termNo;
+
+    /**
+     * 上次失败的原因
+     */
+    private String failMsg;
+
+    /**
+     * 处理成功或者失败,s成功f失败
+     */
+    private String status;
+
+    /**
+     * 删除标志(0-未删除 2-已删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    private String consumeType;
+
+
+
+
+}

+ 89 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/bo/XfFailedRecordBo.java

@@ -0,0 +1,89 @@
+package org.dromara.server.hik.domain.bo;
+
+import org.dromara.server.hik.domain.XfFailedRecord;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 海康消费失败记录业务对象 t_xf_failed_record
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = XfFailedRecord.class, reverseConvertGenerate = false)
+public class XfFailedRecordBo extends BaseEntity {
+
+    /**
+     * 主键
+     */
+    @NotBlank(message = "主键不能为空", groups = { EditGroup.class })
+    private String id;
+
+    /**
+     * 用户流水号
+     */
+    @NotBlank(message = "用户流水号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String userNo;
+
+    /**
+     * 物理卡号
+     */
+    @NotBlank(message = "物理卡号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String factoryId;
+
+    /**
+     * 消费时间
+     */
+    @NotNull(message = "消费时间不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Date consumeDate;
+
+    /**
+     * 设备记录流水号
+     */
+    @NotBlank(message = "设备记录流水号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termRecordId;
+
+    /**
+     * 消费金额
+     */
+    @NotBlank(message = "消费金额不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String consumeMoney;
+
+    /**
+     * 设备mac地址
+     */
+    @NotBlank(message = "设备mac地址不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termMac;
+
+    /**
+     * 设备机号
+     */
+    @NotBlank(message = "设备机号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termNo;
+
+    /**
+     * 上次失败的原因
+     */
+    @NotBlank(message = "上次失败的原因不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String failMsg;
+
+    /**
+     * 处理成功或者失败,s成功f失败
+     */
+    @NotBlank(message = "处理成功或者失败,s成功f失败不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    @NotBlank(message = "消费类型", groups = { AddGroup.class, EditGroup.class })
+    private String consumeType;
+
+
+}

+ 96 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/domain/vo/XfFailedRecordVo.java

@@ -0,0 +1,96 @@
+package org.dromara.server.hik.domain.vo;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.server.hik.domain.XfFailedRecord;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 海康消费失败记录视图对象 t_xf_failed_record
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = XfFailedRecord.class)
+public class XfFailedRecordVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ExcelProperty(value = "主键")
+    private String id;
+
+    /**
+     * 用户流水号
+     */
+    @ExcelProperty(value = "用户流水号")
+    private String userNo;
+
+    /**
+     * 物理卡号
+     */
+    @ExcelProperty(value = "物理卡号")
+    private String factoryId;
+
+    /**
+     * 消费时间
+     */
+    @ExcelProperty(value = "消费时间")
+    private Date consumeDate;
+
+    /**
+     * 设备记录流水号
+     */
+    @ExcelProperty(value = "设备记录流水号")
+    private String termRecordId;
+
+    /**
+     * 消费金额
+     */
+    @ExcelProperty(value = "消费金额")
+    private String consumeMoney;
+
+    /**
+     * 设备mac地址
+     */
+    @ExcelProperty(value = "设备mac地址")
+    private String termMac;
+
+    /**
+     * 设备机号
+     */
+    @ExcelProperty(value = "设备机号")
+    private String termNo;
+
+    /**
+     * 上次失败的原因
+     */
+    @ExcelProperty(value = "上次失败的原因")
+    private String failMsg;
+
+    /**
+     * 处理成功或者失败,s成功f失败
+     */
+    @ExcelProperty(value = "处理成功或者失败,s成功f失败")
+    private String status;
+
+    private String consumeType;
+
+
+}

+ 2 - 20
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/ConsumptionEventHandler.java

@@ -22,8 +22,6 @@ import org.dromara.server.hik.service.IXfTermService;
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -117,24 +115,8 @@ public class ConsumptionEventHandler implements HikEventHandler {
 
     private RemoteConsumeBo setParamBo(ConsumptionEventReceive receive, ConsumptionEventDetail consumptionEvent) {
         RemoteConsumeBo remoteBo = new RemoteConsumeBo();
-        remoteBo.setUserNo(Long.valueOf(consumptionEvent.getEmployeeNoString()));
-        String cardNo = consumptionEvent.getCardNo();
-        if(StringUtils.isNotBlank(cardNo)){
-            remoteBo.setFactoryId(Long.valueOf(cardNo));
-        } else {
-            remoteBo.setFactoryId(0L);
-        }
-        Date dateTime = receive.getDateTime();
-        remoteBo.setConsumeDate(dateTime);
-        remoteBo.setTermRecordId(Long.valueOf(consumptionEvent.getSerialNo()));
-        if(StringUtils.isNotBlank(consumptionEvent.getTotalPayment())){
-            BigDecimal money = new BigDecimal(consumptionEvent.getTotalPayment()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
-            remoteBo.setConsumeMoney(money);
-        }else{
-            remoteBo.setConsumeMoney(BigDecimal.ZERO);
-        }
-
-        remoteBo.setTermMac(receive.getMacAddress());
+        TransactionRecordEventHandler.setCommonFields(remoteBo, consumptionEvent.getEmployeeNoString(), consumptionEvent.getCardNo(),
+            receive.getDateTime(), consumptionEvent.getSerialNo(), consumptionEvent.getTotalPayment(), receive.getMacAddress());
         remoteBo.setCardNo(0L);
         remoteBo.setStatusFlag(4);
         remoteBo.setCreditType(CreditTypeEnum.TERM_CONSUME.code());

+ 101 - 23
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/handler/TransactionRecordEventHandler.java

@@ -5,38 +5,33 @@ import io.seata.common.util.CollectionUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
-import org.dromara.backstage.api.RemotePtXfTermService;
-import org.dromara.backstage.api.domain.vo.RemoteXfTermVo;
-import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ErrorInfo;
+import org.dromara.common.core.enums.CreditTypeEnum;
 import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.file.FileUtils;
 import org.dromara.consume.api.RemoteConsumeService;
-import org.dromara.server.hik.domain.dto.DeviceDto;
-import org.dromara.server.hik.domain.vo.XfConsumeDetailOriginalVo;
+import org.dromara.consume.api.domain.bo.RemoteConsumeBo;
+import org.dromara.consume.api.domain.bo.RemoteResultDto;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
 import org.dromara.server.hik.domain.vo.XfConsumeDetailVo;
 import org.dromara.server.hik.domain.vo.XfTermVo;
-import org.dromara.server.hik.enums.ContentTypeEnum;
 import org.dromara.server.hik.enums.ModeTypeEnum;
 import org.dromara.server.hik.event.HikEventHandler;
 import org.dromara.server.hik.event.domain.FileContent;
 import org.dromara.server.hik.event.domain.TransactionRecordEventConfirmBo;
 import org.dromara.server.hik.event.domain.TransactionRecordEventDetail;
 import org.dromara.server.hik.event.domain.TransactionRecordEventReceive;
-import org.dromara.server.hik.service.IConsumeDetailOriginalService;
-import org.dromara.server.hik.service.ISendDeviceService;
-import org.dromara.server.hik.service.IXfConsumeDetailService;
-import org.dromara.server.hik.service.IXfTermService;
-import org.dromara.server.hik.service.impl.SendDeviceServiceImpl;
-import org.dromara.server.hik.utils.DigestHttpUtil;
+import org.dromara.server.hik.service.*;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import java.io.File;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 
-import static org.dromara.server.hik.constant.HikApiConstants.TRANSACTION_RECORD_Event_confirm;
-
 @Slf4j
 @Component
 @RequiredArgsConstructor
@@ -48,6 +43,8 @@ public class TransactionRecordEventHandler implements HikEventHandler {
 
     private final IConsumeDetailOriginalService consumeDetailOriginalService;
 
+    private final IXfFailedRecordService failedRecordService;
+
     @Value("${upload.upload-path}/")
     private String uploadPath;
 
@@ -63,9 +60,10 @@ public class TransactionRecordEventHandler implements HikEventHandler {
 
         //设备信息
         XfTermVo termVo = termService.queryByMac(transactionRecordEventReceive.getMacAddress());
+        Long termNo = termVo.getTermNo();
         // 消费抓拍图片保存
         if (fileContent != null) {
-            String fileName = termVo.getTermNo()+"_"+transactionRecordEvent.getSerialNo()+  "_" + DateUtils.dateTimeNow() + fileContent.getSuffix();
+            String fileName = termNo +"_"+transactionRecordEvent.getSerialNo()+  "_" + DateUtils.dateTimeNow() + fileContent.getSuffix();
             byte[] content = fileContent.getContent();
             String image_dir_path = uploadPath + "snapshot-pic/";
             File image_dir = new File(image_dir_path);
@@ -79,20 +77,31 @@ public class TransactionRecordEventHandler implements HikEventHandler {
         // 这里只能应答成功,如果是业务异常(余额不足、脏数据等)失败,要存入表中,使用定时任务处理失败,所以即使失败的 也要回复成功
         // 如果是在线交易的,平台根据流水号判断当前交易记录事件是否为未处理事件,若为未处理事件,则平台进行处理并进行扣费;
         // 如果是离线交易记录事件,则平台进行处理并进行扣费;
+        RemoteConsumeBo remoteBo = getRemoteBo(transactionRecordEventReceive);
+        R<ErrorInfo> errorInfo = R.fail("处理失败");
         try  {
             if (ModeTypeEnum.offLine.getCode().equals(modeType)) {
-                // todo 离线交易,调用原始消费记录请求+消费记录入库的接口
-                // 如果失败要记录入库
-
+                // 离线交易,调用原始消费记录请求+消费记录入库的接口,如果失败要记录入库
+                RemoteResultDto remoteResultDto = remoteConsumeService.dealHikUploadOffLineRecord(remoteBo);
+                errorInfo = remoteResultDto.getErrorInfo();
+                RemoteConsumeBo updatedRemoteBo = remoteResultDto.getUpdatedRemoteBo();
+                if(updatedRemoteBo !=null) {
+                    balance  = updatedRemoteBo.getBalance();
+                }
             }else{
                 // 在线交易,调用消费记录入库的接口
                 // 根据机号和交易记录流水号查询消费记录
-                List<XfConsumeDetailVo> vos = consumeDetailService.queryByTermNoAndRecordId(termVo.getTermNo(), transactionRecordEvent.getSerialNo().longValue());
+                // 睡眠一秒,等待消费记录入库成功
+                Thread.sleep(1000);
+                List<XfConsumeDetailVo> vos = consumeDetailService.queryByTermNoAndRecordId(termNo, transactionRecordEvent.getSerialNo().longValue());
                 if (CollectionUtils.isEmpty(vos)){
-                    // 调用消费记录入库的接口
-                    // todo 组装参数,调用消费记录入库的接口
-                    // 如果失败要记录入库
-
+                    // 组装参数,调用消费记录入库的接口 // 如果失败要记录入库
+                    RemoteResultDto remoteResultDto = remoteConsumeService.dealHikUploadRecord(remoteBo);
+                    errorInfo = remoteResultDto.getErrorInfo();
+                    RemoteConsumeBo updatedRemoteBo = remoteResultDto.getUpdatedRemoteBo();
+                    if(updatedRemoteBo !=null) {
+                        balance  = updatedRemoteBo.getBalance();
+                    }
                 }else{
                     XfConsumeDetailVo consumeDetailVo = vos.get(0);
                     balance = consumeDetailVo.getCardValue();
@@ -101,8 +110,10 @@ public class TransactionRecordEventHandler implements HikEventHandler {
         } catch (Exception e) {
             log.error("消费机交易记录事件处理异常:{}", e.getMessage());
             // 失败,记录入库
+            errorInfo = R.fail(e.getMessage());
         }
 
+        insertFailedRecordBo(transactionRecordEventReceive, termNo, errorInfo, modeType);
         //应答
         return answerEvent(transactionRecordEventReceive,balance);
     }
@@ -128,4 +139,71 @@ public class TransactionRecordEventHandler implements HikEventHandler {
         map.put("TransactionRecordEventConfirm", confirmBo);
         return map;
     }
+
+
+    private RemoteConsumeBo getRemoteBo(TransactionRecordEventReceive info){
+        RemoteConsumeBo remoteBo = new RemoteConsumeBo();
+        remoteBo.setStatusFlag(4);
+        remoteBo.setCardNo(0L);
+        remoteBo.setCreditType(CreditTypeEnum.TERM_CONSUME.code());
+        remoteBo.setRecordStatus(364L);
+        remoteBo.setRecordId(0L);
+
+        TransactionRecordEventDetail recordEvent = info.getTransactionRecordEvent();
+        setCommonFields(remoteBo, recordEvent.getEmployeeNoString(), recordEvent.getCardNo(), info.getDateTime(),
+            recordEvent.getSerialNo(), recordEvent.getTotalPayment(), info.getMacAddress());
+
+        return remoteBo;
+    }
+
+    static void setCommonFields(RemoteConsumeBo remoteBo, String employeeNoString, String cardNo2, Date dateTime2, Integer serialNo,
+                                String totalPayment, String macAddress) {
+        remoteBo.setUserNo(Long.valueOf(employeeNoString));
+        if(StringUtils.isNotBlank(cardNo2)){
+            remoteBo.setFactoryId(Long.valueOf(cardNo2));
+        } else {
+            remoteBo.setFactoryId(0L);
+        }
+        remoteBo.setConsumeDate(dateTime2);
+        remoteBo.setTermRecordId(Long.valueOf(serialNo));
+        if(StringUtils.isNotBlank(totalPayment)){
+            BigDecimal money = new BigDecimal(totalPayment).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+            remoteBo.setConsumeMoney(money);
+        }else{
+            remoteBo.setConsumeMoney(BigDecimal.ZERO);
+        }
+        remoteBo.setTermMac(macAddress);
+    }
+
+    private void insertFailedRecordBo(TransactionRecordEventReceive info, Long termNo, R<ErrorInfo> errorInfo, String modeType){
+        Boolean error = R.isError(errorInfo);
+        if (!error) {
+            return;
+        }
+        XfFailedRecordBo bo = new XfFailedRecordBo();
+        TransactionRecordEventDetail recordEvent = info.getTransactionRecordEvent();
+        bo.setUserNo(recordEvent.getEmployeeNoString());
+        bo.setConsumeDate(info.getDateTime());
+        bo.setTermRecordId(recordEvent.getSerialNo().toString());
+        bo.setConsumeMoney(recordEvent.getTotalPayment());
+        bo.setTermMac(info.getMacAddress());
+        if(termNo != null){
+            bo.setTermNo(termNo.toString());
+        }
+        bo.setStatus("f");
+        bo.setFactoryId(recordEvent.getCardNo());
+        String msg = errorInfo.getMsg();
+        ErrorInfo data = errorInfo.getData();
+        if(data != null){
+            msg = data.getMessage();
+        }
+        if(StringUtils.isNotBlank(msg) && msg.length() >= 2000){
+            msg = msg.substring(0, 2000);
+        }
+        bo.setFailMsg(msg);
+        bo.setConsumeType(modeType);
+
+        failedRecordService.insertByBo(bo);
+    }
+
 }

+ 99 - 3
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/event/timedtask/HandleTask.java

@@ -1,10 +1,29 @@
 package org.dromara.server.hik.event.timedtask;
 
+import io.seata.common.util.CollectionUtils;
+import io.seata.common.util.StringUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ErrorInfo;
+import org.dromara.common.core.enums.CreditTypeEnum;
+import org.dromara.consume.api.RemoteConsumeService;
+import org.dromara.consume.api.domain.bo.RemoteConsumeBo;
+import org.dromara.consume.api.domain.bo.RemoteResultDto;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
+import org.dromara.server.hik.domain.vo.XfConsumeDetailVo;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.server.hik.enums.ModeTypeEnum;
+import org.dromara.server.hik.service.IXfConsumeDetailService;
+import org.dromara.server.hik.service.IXfFailedRecordService;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
 /**
  * 定时任务: 主要用于处理 TransactionRecordEvent事件 业务失败的记录 于凌晨执行即可
  */
@@ -13,14 +32,91 @@ import org.springframework.stereotype.Service;
 @Slf4j
 public class HandleTask {
 
+    private final IXfFailedRecordService failedRecordService;
+
+    private final IXfConsumeDetailService consumeDetailService;
+
+    @DubboReference
+    private final RemoteConsumeService remoteConsumeService;
 
     // 每天凌晨3点执行一次
     @Scheduled(cron = "0 0 3 * * ?")
     public void handleTransactionRecordEventTask()
     {
         /*有离线的,也有非离线的,重复在执行下 处理处理TransactionRecordEvent的逻辑 */
-        log.info("开始处理 TransactionRecordEvent事件,");
-        // todo 获取所有失败的 TransactionRecordEvent
-        // 设计t_xf_failed_record表  ID 、event_type、event_data、fail_msg、status、create_time、update_time、delete_flag、create_by、update_by
+        log.info("开始处理 TransactionRecordEvent事件的消费失败记录,");
+        //  获取所有失败的 TransactionRecordEvent
+        // 设计t_xf_failed_record表  ID 、tenant_id、user_no、factory_id、consume_date、term_record_id、consume_money、term_mac、term_no、fail_msg、status、create_time、update_time、delete_flag、create_by、update_by
+        XfFailedRecordBo bo = new XfFailedRecordBo();
+        bo.setStatus("f");
+        List<XfFailedRecordVo> vos = failedRecordService.queryList(bo);
+        RemoteConsumeBo remoteBo = new RemoteConsumeBo();
+        remoteBo.setStatusFlag(4);
+        remoteBo.setCardNo(0L);
+        remoteBo.setCreditType(CreditTypeEnum.TERM_CONSUME.code());
+        remoteBo.setRecordStatus(364L);
+        remoteBo.setRecordId(0L);
+        for (XfFailedRecordVo vo : vos) {
+            boolean result = false;
+            try{
+                if(StringUtils.isNotBlank(vo.getUserNo())){
+                    remoteBo.setUserNo(Long.valueOf(vo.getUserNo()));
+                }else{
+                    remoteBo.setUserNo(0L);
+                }
+                if(StringUtils.isNotBlank(vo.getFactoryId())){
+                    remoteBo.setFactoryId(Long.valueOf(vo.getFactoryId()));
+                }else{
+                    remoteBo.setFactoryId(0L);
+                }
+                if(StringUtils.isNotBlank(vo.getConsumeMoney())){
+                    BigDecimal money = new BigDecimal(vo.getConsumeMoney()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+                    remoteBo.setConsumeMoney(money);
+                }else{
+                    remoteBo.setConsumeMoney(BigDecimal.ZERO);
+                }
+                remoteBo.setTermMac(vo.getTermMac());
+                if(StringUtils.isNotBlank(vo.getTermNo())){
+                    remoteBo.setTermNo(Long.valueOf(vo.getTermNo()));
+                }
+                if(StringUtils.isNotBlank(vo.getTermRecordId())){
+                    remoteBo.setTermRecordId(Long.valueOf(vo.getTermRecordId()));
+                }else{
+                    remoteBo.setTermRecordId(0L);
+                }
+                remoteBo.setConsumeDate(vo.getConsumeDate());
+                RemoteResultDto rs;
+                if(ModeTypeEnum.offLine.getCode().equals(vo.getConsumeType())){
+                    //  离线交易,调用原始消费记录请求+消费记录入库的接口
+                    rs = remoteConsumeService.dealHikUploadOffLineRecord(remoteBo);
+                }else{
+                    //  在线交易,调用消费记录入库的接口
+                    // 在查询一次 消费记录,如果有则不再执行
+                    List<XfConsumeDetailVo> list = consumeDetailService.queryByTermNoAndRecordId(Long.valueOf(vo.getTermNo()), Long.valueOf(vo.getTermRecordId()));
+                    if(CollectionUtils.isEmpty(vos)){
+                        rs = remoteConsumeService.dealHikUploadRecord(remoteBo);
+                    }else{
+                        log.info("消费机事件,已经存在消费记录,不再处理:{}", vo);
+                        rs = new RemoteResultDto(R.ok(),null);
+                    }
+                }
+
+                R<ErrorInfo> errorInfo = rs.getErrorInfo();
+                if (!R.isError(errorInfo)) {
+                    result = true;
+                }
+
+            }catch (Exception e){
+                e.printStackTrace();
+                log.error("定时处理 TransactionRecordEvent事件失败:{}", e.getMessage());
+            }
+            if(result){
+                // 如果成功则更新status为f
+                XfFailedRecordBo recordBo = new XfFailedRecordBo();
+                recordBo.setId(vo.getId());
+                recordBo.setStatus("s");
+                failedRecordService.updateByBo(recordBo);
+            }
+        }
     }
 }

+ 15 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/mapper/XfFailedRecordMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.server.hik.mapper;
+
+import org.dromara.server.hik.domain.XfFailedRecord;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 海康消费失败记录Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+public interface XfFailedRecordMapper extends BaseMapperPlus<XfFailedRecord, XfFailedRecordVo> {
+
+}

+ 77 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/IXfFailedRecordService.java

@@ -0,0 +1,77 @@
+package org.dromara.server.hik.service;
+
+import org.dromara.server.hik.domain.XfFailedRecord;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 海康消费失败记录Service接口
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+public interface IXfFailedRecordService {
+
+    /**
+     * 查询海康消费失败记录
+     *
+     * @param id 主键
+     * @return 海康消费失败记录
+     */
+    XfFailedRecordVo queryById(String id);
+
+    /**
+     * 分页查询海康消费失败记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 海康消费失败记录分页列表
+     */
+    TableDataInfo<XfFailedRecordVo> queryPageList(XfFailedRecordBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的海康消费失败记录列表
+     *
+     * @param bo 查询条件
+     * @return 海康消费失败记录列表
+     */
+    List<XfFailedRecordVo> queryList(XfFailedRecordBo bo);
+
+    /**
+     * 新增海康消费失败记录
+     *
+     * @param bo 海康消费失败记录
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(XfFailedRecordBo bo);
+
+    /**
+     * 新增海康消费失败记录
+     *
+     * @param record 海康消费失败记录
+     * @return 是否新增成功
+     */
+    Boolean insertByEntity(XfFailedRecord record);
+
+    /**
+     * 修改海康消费失败记录
+     *
+     * @param bo 海康消费失败记录
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(XfFailedRecordBo bo);
+
+    /**
+     * 校验并批量删除海康消费失败记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
+}

+ 181 - 0
ruoyi-server/ruoyi-server-hik/src/main/java/org/dromara/server/hik/service/impl/XfFailedRecordServiceImpl.java

@@ -0,0 +1,181 @@
+package org.dromara.server.hik.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.server.hik.domain.XfFailedRecord;
+import org.dromara.server.hik.mapper.XfFailedRecordMapper;
+import org.dromara.server.hik.service.IXfFailedRecordService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 海康消费失败记录Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@RequiredArgsConstructor
+@Service
+public class XfFailedRecordServiceImpl implements IXfFailedRecordService {
+
+    private final XfFailedRecordMapper baseMapper;
+
+    /**
+     * 查询海康消费失败记录
+     *
+     * @param id 主键
+     * @return 海康消费失败记录
+     */
+    @Override
+    public XfFailedRecordVo queryById(String id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询海康消费失败记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 海康消费失败记录分页列表
+     */
+    @Override
+    public TableDataInfo<XfFailedRecordVo> queryPageList(XfFailedRecordBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<XfFailedRecord> lqw = buildQueryWrapper(bo);
+        Page<XfFailedRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的海康消费失败记录列表
+     *
+     * @param bo 查询条件
+     * @return 海康消费失败记录列表
+     */
+    @Override
+    public List<XfFailedRecordVo> queryList(XfFailedRecordBo bo) {
+        LambdaQueryWrapper<XfFailedRecord> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<XfFailedRecord> buildQueryWrapper(XfFailedRecordBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<XfFailedRecord> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getUserNo()), XfFailedRecord::getUserNo, bo.getUserNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getFactoryId()), XfFailedRecord::getFactoryId, bo.getFactoryId());
+        lqw.eq(bo.getConsumeDate() != null, XfFailedRecord::getConsumeDate, bo.getConsumeDate());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermRecordId()), XfFailedRecord::getTermRecordId, bo.getTermRecordId());
+        lqw.eq(StringUtils.isNotBlank(bo.getConsumeMoney()), XfFailedRecord::getConsumeMoney, bo.getConsumeMoney());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermMac()), XfFailedRecord::getTermMac, bo.getTermMac());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermNo()), XfFailedRecord::getTermNo, bo.getTermNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getFailMsg()), XfFailedRecord::getFailMsg, bo.getFailMsg());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XfFailedRecord::getStatus, bo.getStatus());
+        return lqw;
+    }
+
+    private QueryWrapper<XfFailedRecord> buildQueryWrapper(XfFailedRecordBo bo,String tableAlias) {
+        QueryWrapper<XfFailedRecord> lqw = new QueryWrapper<>();
+        String columnPrefix = "";
+        if(StringUtils.isNotBlank(tableAlias)){
+            columnPrefix = tableAlias + ".";
+        }
+        lqw.eq(StringUtils.isNotBlank(bo.getUserNo()), columnPrefix+"user_no", bo.getUserNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getFactoryId()), columnPrefix+"factory_id", bo.getFactoryId());
+        lqw.eq(bo.getConsumeDate() != null, columnPrefix+"consume_date", bo.getConsumeDate());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermRecordId()), columnPrefix+"term_record_id", bo.getTermRecordId());
+        lqw.eq(StringUtils.isNotBlank(bo.getConsumeMoney()), columnPrefix+"consume_money", bo.getConsumeMoney());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermMac()), columnPrefix+"term_mac", bo.getTermMac());
+        lqw.eq(StringUtils.isNotBlank(bo.getTermNo()), columnPrefix+"term_no", bo.getTermNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getFailMsg()), columnPrefix+"fail_msg", bo.getFailMsg());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), columnPrefix+"status", bo.getStatus());
+        return lqw;
+    }
+
+    /**
+     * 新增海康消费失败记录
+     *
+     * @param bo 海康消费失败记录
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(XfFailedRecordBo bo) {
+        XfFailedRecord add = MapstructUtils.convert(bo, XfFailedRecord.class);
+        validEntityBeforeSave(add);
+        //先查询一遍在新增,避免重复新增
+        if (isExist(bo.getTermRecordId(), bo.getTermNo(), bo.getTermMac())) return false;
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    private boolean isExist(String termRecordId, String termNo, String termMac) {
+        LambdaQueryWrapper<XfFailedRecord> eq = Wrappers.lambdaQuery(XfFailedRecord.class).eq(XfFailedRecord::getTermRecordId, termRecordId)
+            .eq(StringUtils.isNotBlank(termNo), XfFailedRecord::getTermNo, termNo)
+            .eq(XfFailedRecord::getTermMac, termMac);
+        if (baseMapper.selectCount(eq) > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 新增海康消费失败记录, 先查询一遍在新增,避免重复新增
+     *
+     * @param record 海康消费失败记录
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByEntity(XfFailedRecord record) {
+        //先查询一遍在新增,避免重复新增
+        if (isExist(record.getTermRecordId(), record.getTermNo(), record.getTermMac())) return false;
+        return baseMapper.insert(record) > 0;
+    }
+
+    /**
+     * 修改海康消费失败记录
+     *
+     * @param bo 海康消费失败记录
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(XfFailedRecordBo bo) {
+        XfFailedRecord update = MapstructUtils.convert(bo, XfFailedRecord.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(XfFailedRecord entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除海康消费失败记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 25 - 0
ruoyi-server/ruoyi-server-hik/src/main/resources/mapper/FailedRecord/XfFailedRecordMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.server.hik.mapper.XfFailedRecordMapper">
+
+    <resultMap type="org.dromara.server.hik.domain.XfFailedRecord" id="XfFailedRecordResult">
+            <result property="id"    column="id"    />
+            <result property="tenantId"    column="tenant_id"    />
+            <result property="userNo"    column="user_no"    />
+            <result property="factoryId"    column="factory_id"    />
+            <result property="consumeDate"    column="consume_date"    />
+            <result property="termRecordId"    column="term_record_id"    />
+            <result property="consumeMoney"    column="consume_money"    />
+            <result property="termMac"    column="term_mac"    />
+            <result property="termNo"    column="term_no"    />
+            <result property="failMsg"    column="fail_msg"    />
+            <result property="status"    column="status"    />
+            <result property="delFlag"    column="del_flag"    />
+            <result property="createBy"    column="create_by"    />
+            <result property="createTime"    column="create_time"    />
+            <result property="updateBy"    column="update_by"    />
+            <result property="updateTime"    column="update_time"    />
+    </resultMap>
+</mapper>

+ 41 - 0
sql/2025-6-9/kingbase/ddl.sql

@@ -0,0 +1,41 @@
+CREATE TABLE "dbo"."t_xf_failed_record" (
+    "id" character varying(64 char) NOT NULL,
+    "tenant_id" character varying(20 char) NOT NULL DEFAULT ''::varchar,
+    "consume_type" character varying(10 char) NULL,
+	"user_no" character varying(20 char) NULL,
+	"factory_id" character varying(20 char) NULL,
+	"consume_date" timestamp NULL,
+	"term_record_id" character varying(20 char) NULL,
+	"consume_money" character varying(10 char) NULL,
+	"term_mac" character varying(20 char) NOT NULL DEFAULT ''::varchar,
+	"term_no" character varying(20 char) NULL,
+	"fail_msg" character varying(2000 char) NULL,
+	"status" character(1 char) NOT NULL DEFAULT 'f'::bpchar,
+	"del_flag" character(1 char) NOT NULL DEFAULT '0'::bpchar,
+	"create_by" bigint NULL,
+	"create_dept" bigint NULL,
+	"create_time" timestamp NULL,
+	"update_by" bigint NULL,
+	"update_time" timestamp NULL,
+	CONSTRAINT "t_xf_failed_record_pkey" PRIMARY KEY (id)
+);
+ALTER TABLE "dbo"."t_xf_failed_record" COMMENT '海康消费失败记录';
+
+-- Column comments
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "id" COMMENT '主键';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "tenant_id" COMMENT '租户Id';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "user_no" COMMENT '用户流水号';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "factory_id" COMMENT '物理卡号';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "term_record_id" COMMENT '设备记录流水号';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "consume_money" COMMENT '消费金额';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "consume_date" COMMENT '消费时间';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "term_mac" COMMENT '设备mac地址';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "term_no" COMMENT '设备机号';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "fail_msg" COMMENT '上次失败的原因';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "status" COMMENT '处理结果,s成功f失败';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "del_flag" COMMENT '删除标志(0-未删除 2-已删除)';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "create_by" COMMENT '创建者';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "create_time" COMMENT '创建时间';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "update_by" COMMENT '更新者';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "update_time" COMMENT '更新时间';
+ALTER TABLE "dbo"."t_xf_failed_record" MODIFY "consume_type" COMMENT '消费类型,在线current离线offLine';

+ 21 - 0
sql/2025-6-9/mysql/ddl.sql

@@ -0,0 +1,21 @@
+CREATE TABLE `ykt_cloud`.`t_xf_failed_record`  (
+                                         `id` varchar(64) NOT NULL COMMENT '主键',
+                                         `tenant_id` varchar(20) NOT NULL DEFAULT '' COMMENT '租户Id',
+                                         `user_no` varchar(20) NULL COMMENT '用户流水号',
+                                         `factory_id` varchar(20) NULL COMMENT '物理卡号',
+                                         `consume_date` datetime NULL COMMENT '消费时间',
+                                         `consume_type` varchar(10) NULL COMMENT '消费类型,在线current离线offLine',
+                                         `term_record_id` varchar(20) NULL COMMENT '设备记录流水号',
+                                         `consume_money` varchar(10) NULL COMMENT '消费金额',
+                                         `term_mac` varchar(20) NULL COMMENT '设备mac地址',
+                                         `term_no` varchar(20) NULL COMMENT '设备机号',
+                                         `fail_msg` varchar(2000) NULL COMMENT '失败原因',
+                                         `status` char(1) NOT NULL DEFAULT 'f' COMMENT '处理结果,s成功f失败',
+                                         `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志',
+                                         `create_by` bigint(0) NULL DEFAULT NULL COMMENT '创建人',
+                                         `create_dept` bigint(0) NULL DEFAULT NULL COMMENT '创建人部门',
+                                         `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
+                                         `update_by` bigint(0) NULL DEFAULT NULL COMMENT '修改人',
+                                         `update_time` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
+                                         PRIMARY KEY (`id`)
+) COMMENT = '海康消费失败记录';