diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java index fa0c4291..78e69581 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/monitor/TabOrderMonitorController.java @@ -165,8 +165,8 @@ public class TabOrderMonitorController extends BaseController int fail = data.get("fail") instanceof Number ? ((Number) data.get("fail")).intValue() : 0; String action = rows.size() == 1 ? "ONE" : "SELECTED"; String bizId = firstOrderBizId(rows, action); - String status = fail > 0 && ok == 0 ? "FAIL" : "SUCCESS"; - String err = fail > 0 ? ("fail=" + fail) : null; + String status = fail > 0 ? "FAIL" : "SUCCESS"; + String err = fail > 0 ? ("fail=" + fail + ", ok=" + ok) : null; TabOrderSyncEsLog.save(billSearchStore, bizId, action, rows, data, status, err); return success(data); } diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java index 1ee75a26..20d584ef 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/BillingController.java @@ -1,5 +1,6 @@ package com.bms.web.controller.price; +import java.io.ByteArrayInputStream; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -25,8 +26,11 @@ import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.utils.DateUtils; import com.bms.common.utils.MessageUtils; +import com.bms.common.utils.SecurityUtils; import com.bms.common.utils.StringUtils; import com.bms.common.utils.poi.ExcelUtil; +import com.bms.system.domain.BmsBizAttachment; +import com.bms.system.service.IBmsBizAttachmentService; import com.bms.price.domain.BatchAddIncomeBillFeeTask; import com.bms.price.domain.ChargeWeightImportTask; import com.bms.price.domain.PrcBillingTask; @@ -66,6 +70,9 @@ public class BillingController extends BaseController @Autowired private IPrcBillingTaskService prcBillingTaskService; + @Autowired + private IBmsBizAttachmentService bizAttachmentService; + /** * Historical quote list. */ @@ -360,12 +367,36 @@ public class BillingController extends BaseController @PostMapping("/bill/chargeWeight/importData") public AjaxResult importChargeWeight(MultipartFile file) throws Exception { + if (file == null || file.isEmpty()) + { + return error("file required"); + } + byte[] bytes = file.getBytes(); + String originalName = file.getOriginalFilename(); ExcelUtil util = new ExcelUtil(ChargeWeightImportRow.class); - List rows = util.importExcel(file.getInputStream()); + List rows = util.importExcel(new ByteArrayInputStream(bytes)); String taskId = prcOrderBillService.submitImportChargeWeight(rows); - Map payload = new HashMap(); - payload.put("taskId", taskId); - return success(payload); + try + { + BmsBizAttachment att = bizAttachmentService.saveUpload( + BmsBizAttachment.BIZ_CHARGE_WEIGHT_IMPORT, taskId, bytes, originalName, + SecurityUtils.getUsername(), null); + Map payload = new HashMap(); + payload.put("taskId", taskId); + if (att != null) + { + payload.put("attachmentId", att.getAttachmentId()); + } + return success(payload); + } + catch (Exception e) + { + // Import task already queued; attachment failure should not block rebill + Map payload = new HashMap(); + payload.put("taskId", taskId); + payload.put("attachmentWarn", e.getMessage()); + return success(payload); + } } @PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')") @@ -379,4 +410,21 @@ public class BillingController extends BaseController } return success(task); } + + /** + * List charge-weight import Excel attachment records. + */ + @PreAuthorize("@ss.hasPermi('price:billing:chargeWeightEdit')") + @GetMapping("/bill/chargeWeight/import/attachments") + public TableDataInfo listChargeWeightImportAttachments(BmsBizAttachment query) + { + if (query == null) + { + query = new BmsBizAttachment(); + } + query.setBizType(BmsBizAttachment.BIZ_CHARGE_WEIGHT_IMPORT); + startPage(); + List list = bizAttachmentService.selectBmsBizAttachmentList(query); + return getDataTable(list); + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/search/TabOrderSyncEsLog.java b/bms-back/bms-system/src/main/java/com/bms/price/search/TabOrderSyncEsLog.java index ddbf6ed9..e152a1d7 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/search/TabOrderSyncEsLog.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/search/TabOrderSyncEsLog.java @@ -42,4 +42,32 @@ public final class TabOrderSyncEsLog String st = StringUtils.isEmpty(status) ? EsBizLogSupport.STATUS_SUCCESS : status; store.save(EsLogType.ORDER_SOURCE_SYNC, bizId, reqJson, respJson, st, errorMessage); } + + /** Persist one failed sync order so it appears in order-source operation log. */ + public static void saveOneFail(IBillSearchStore store, Object row, Object response, String errorMessage) + { + String orderId = null; + if (row instanceof com.bms.price.domain.vo.TabOrderMonitorVo) + { + orderId = ((com.bms.price.domain.vo.TabOrderMonitorVo) row).getOrderId(); + } + else if (row instanceof Map) + { + Object v = ((Map) row).get("orderId"); + if (v != null) + { + orderId = String.valueOf(v); + } + } + String bizId; + if (StringUtils.isEmpty(orderId)) + { + bizId = "ONE_FAIL_" + System.currentTimeMillis(); + } + else + { + bizId = "ONE_" + orderId.trim(); + } + save(store, bizId, "ONE_FAIL", row, response, EsBizLogSupport.STATUS_FAIL, errorMessage); + } } diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/TabOrderSyncAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/TabOrderSyncAsyncRunner.java index 450b2cb7..a9812d1c 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/task/TabOrderSyncAsyncRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/TabOrderSyncAsyncRunner.java @@ -84,6 +84,8 @@ public class TabOrderSyncAsyncRunner AtomicInteger processed = new AtomicInteger(0); AtomicInteger ok = new AtomicInteger(0); AtomicInteger fail = new AtomicInteger(0); + List> failDetails = java.util.Collections.synchronizedList( + new ArrayList>()); int pageNum = 1; while (true) { @@ -102,7 +104,7 @@ public class TabOrderSyncAsyncRunner { final TabOrderMonitorVo one = row; jobs.add(() -> { - boolean success = syncOne(one); + boolean success = syncOne(one, failDetails); if (success) { ok.incrementAndGet(); @@ -137,7 +139,10 @@ public class TabOrderSyncAsyncRunner resp.put("ok", Integer.valueOf(ok.get())); resp.put("fail", Integer.valueOf(fail.get())); resp.put("threads", Integer.valueOf(threads)); - TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "FILTER_DONE", query, resp, "SUCCESS", null); + resp.put("failDetails", truncateFailDetails(failDetails)); + String doneStatus = fail.get() > 0 ? "FAIL" : "SUCCESS"; + String doneErr = fail.get() > 0 ? ("fail=" + fail.get() + ", ok=" + ok.get()) : null; + TabOrderSyncEsLog.save(billSearchStore, asyncTaskId, "FILTER_DONE", query, resp, doneStatus, doneErr); log.info("orderSource sync done asyncTaskId={} total={} ok={} fail={} threads={}", asyncTaskId, total, ok.get(), fail.get(), threads); } @@ -253,12 +258,18 @@ public class TabOrderSyncAsyncRunner return data; } - private boolean syncOne(TabOrderMonitorVo row) + private boolean syncOne(TabOrderMonitorVo row, List> failDetails) { try { PrcBillingTask task = billingTaskService.submitAndProcess(toSubmit(row)); - return task != null && BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()); + boolean success = task != null + && BillTaskSuccessStatus.SUCCESS.getCode().equals(task.getSuccessStatus()); + if (!success) + { + collectFailDetail(failDetails, row, task, null); + } + return success; } catch (RuntimeException e) { @@ -266,10 +277,62 @@ public class TabOrderSyncAsyncRunner row == null ? null : row.getOrderId(), row == null ? null : row.getTransferOrderId(), e.getMessage()); + collectFailDetail(failDetails, row, null, e.getMessage()); return false; } } + private static final int FAIL_DETAIL_MAX = 200; + + private static void collectFailDetail(List> failDetails, TabOrderMonitorVo row, + PrcBillingTask task, String exceptionMessage) + { + if (failDetails == null) + { + return; + } + Map item = new LinkedHashMap(); + item.put("orderId", row == null ? null : row.getOrderId()); + item.put("transferOrderId", row == null ? null : row.getTransferOrderId()); + String err = exceptionMessage; + if (task != null) + { + item.put("successStatus", task.getSuccessStatus()); + item.put("billType", task.getBillType()); + item.put("taskId", task.getTaskId()); + if (StringUtils.isEmpty(err)) + { + err = task.getErrorMessage(); + } + } + if (StringUtils.isEmpty(err)) + { + err = "sync failed"; + } + item.put("errorMessage", err); + synchronized (failDetails) + { + if (failDetails.size() >= FAIL_DETAIL_MAX) + { + return; + } + failDetails.add(item); + } + } + + private static List> truncateFailDetails(List> failDetails) + { + if (failDetails == null || failDetails.isEmpty()) + { + return java.util.Collections.emptyList(); + } + if (failDetails.size() <= FAIL_DETAIL_MAX) + { + return new ArrayList>(failDetails); + } + return new ArrayList>(failDetails.subList(0, FAIL_DETAIL_MAX)); + } + private static void invokeAllQuiet(ExecutorService pool, List> jobs) throws InterruptedException { List> futures = pool.invokeAll(jobs); diff --git a/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsBizAttachment.java b/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsBizAttachment.java new file mode 100644 index 00000000..802ace09 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/domain/BmsBizAttachment.java @@ -0,0 +1,30 @@ +package com.bms.system.domain; + +import com.bms.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 公共业务附件 bms_biz_attachment + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class BmsBizAttachment extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 批量修改计费重导入 */ + public static final String BIZ_CHARGE_WEIGHT_IMPORT = "CHARGE_WEIGHT_IMPORT"; + + private Long attachmentId; + /** 业务类型 */ + private String bizType; + /** 业务关联ID */ + private String bizId; + private String fileName; + private String filePath; + private Long fileSize; + private String contentType; + private String fileExtension; + private String delFlag; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsBizAttachmentMapper.java b/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsBizAttachmentMapper.java new file mode 100644 index 00000000..e30a528e --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/mapper/BmsBizAttachmentMapper.java @@ -0,0 +1,18 @@ +package com.bms.system.mapper; + +import java.util.List; +import com.bms.system.domain.BmsBizAttachment; + +/** Common biz attachment mapper */ +public interface BmsBizAttachmentMapper +{ + BmsBizAttachment selectBmsBizAttachmentById(Long attachmentId); + + List selectBmsBizAttachmentList(BmsBizAttachment query); + + int insertBmsBizAttachment(BmsBizAttachment row); + + int updateBmsBizAttachment(BmsBizAttachment row); + + int deleteBmsBizAttachmentByIds(Long[] attachmentIds); +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizAttachmentService.java b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizAttachmentService.java new file mode 100644 index 00000000..ab2edffe --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/IBmsBizAttachmentService.java @@ -0,0 +1,18 @@ +package com.bms.system.service; + +import java.util.List; +import com.bms.system.domain.BmsBizAttachment; + +/** 公共业务附件 */ +public interface IBmsBizAttachmentService +{ + BmsBizAttachment selectBmsBizAttachmentById(Long attachmentId); + + List selectBmsBizAttachmentList(BmsBizAttachment query); + + /** + * 落盘保存上传文件并写附件记录 + */ + BmsBizAttachment saveUpload(String bizType, String bizId, byte[] bytes, String filename, + String operName, String remark) throws Exception; +} diff --git a/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizAttachmentServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizAttachmentServiceImpl.java new file mode 100644 index 00000000..3175a58b --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/system/service/impl/BmsBizAttachmentServiceImpl.java @@ -0,0 +1,104 @@ +package com.bms.system.service.impl; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.bms.common.config.BmsConfig; +import com.bms.common.exception.ServiceException; +import com.bms.common.utils.DateUtils; +import com.bms.common.utils.StringUtils; +import com.bms.common.utils.file.FileUploadUtils; +import com.bms.common.utils.uuid.IdUtils; +import com.bms.system.domain.BmsBizAttachment; +import com.bms.system.mapper.BmsBizAttachmentMapper; +import com.bms.system.service.IBmsBizAttachmentService; + +@Service +public class BmsBizAttachmentServiceImpl implements IBmsBizAttachmentService +{ + private static final String SUB_DIR = "biz/attachment"; + + @Autowired + private BmsBizAttachmentMapper attachmentMapper; + + @Override + public BmsBizAttachment selectBmsBizAttachmentById(Long attachmentId) + { + return attachmentMapper.selectBmsBizAttachmentById(attachmentId); + } + + @Override + public List selectBmsBizAttachmentList(BmsBizAttachment query) + { + List list = attachmentMapper.selectBmsBizAttachmentList(query); + return list == null ? Collections.emptyList() : list; + } + + @Override + public BmsBizAttachment saveUpload(String bizType, String bizId, byte[] bytes, String filename, + String operName, String remark) throws Exception + { + if (StringUtils.isEmpty(bizType)) + { + throw new ServiceException("bizType required"); + } + if (bytes == null || bytes.length == 0) + { + throw new ServiceException("file empty"); + } + String originalName = StringUtils.isNotEmpty(filename) ? filename : "upload.bin"; + String ext = FilenameUtils.getExtension(originalName); + if (StringUtils.isEmpty(ext)) + { + ext = "bin"; + } + ext = ext.toLowerCase(Locale.ROOT); + + String uploadDir = BmsConfig.getUploadPath() + File.separator + SUB_DIR; + String pathName = DateUtils.datePath() + "/" + IdUtils.fastSimpleUUID() + "." + ext; + File dest = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + FileOutputStream fos = null; + try + { + fos = new FileOutputStream(dest); + fos.write(bytes); + } + finally + { + IOUtils.close(fos); + } + String storedPath = FileUploadUtils.getPathFileName(uploadDir, pathName); + + BmsBizAttachment row = new BmsBizAttachment(); + row.setBizType(bizType.trim()); + row.setBizId(bizId); + row.setFileName(originalName); + row.setFilePath(storedPath); + row.setFileSize(Long.valueOf(bytes.length)); + row.setContentType(resolveContentType(ext)); + row.setFileExtension(ext); + row.setRemark(remark); + row.setCreateBy(operName); + attachmentMapper.insertBmsBizAttachment(row); + return row; + } + + private static String resolveContentType(String ext) + { + if ("xls".equals(ext)) + { + return "application/vnd.ms-excel"; + } + if ("xlsx".equals(ext)) + { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } + return ""; + } +} diff --git a/bms-back/bms-system/src/main/resources/mapper/system/BmsBizAttachmentMapper.xml b/bms-back/bms-system/src/main/resources/mapper/system/BmsBizAttachmentMapper.xml new file mode 100644 index 00000000..e423e6f0 --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/system/BmsBizAttachmentMapper.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + select attachment_id, biz_type, biz_id, file_name, file_path, file_size, content_type, + file_extension, remark, create_by, create_time, update_by, update_time, del_flag + from bms_biz_attachment + + + + + + + + insert into bms_biz_attachment ( + biz_type, + biz_id, + file_name, + file_path, + file_size, + content_type, + file_extension, + remark, + create_by, + create_time, + del_flag + ) values ( + #{bizType}, + #{bizId}, + #{fileName}, + #{filePath}, + #{fileSize}, + #{contentType}, + #{fileExtension}, + #{remark}, + #{createBy}, + sysdate(), + '0' + ) + + + + update bms_biz_attachment + + biz_id = #{bizId}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where attachment_id = #{attachmentId} + + + + update bms_biz_attachment set del_flag = '2' where attachment_id in + + #{id} + + + diff --git a/bms-back/sql/system/bms_biz_attachment.sql b/bms-back/sql/system/bms_biz_attachment.sql new file mode 100644 index 00000000..44b849ce --- /dev/null +++ b/bms-back/sql/system/bms_biz_attachment.sql @@ -0,0 +1,22 @@ +-- 公共业务附件表(可复用:批量修改计费重导入等) +SET NAMES utf8mb4; + +CREATE TABLE IF NOT EXISTS bms_biz_attachment ( + attachment_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '附件ID', + biz_type varchar(64) NOT NULL COMMENT '业务类型(如 CHARGE_WEIGHT_IMPORT)', + biz_id varchar(64) DEFAULT NULL COMMENT '业务关联ID(如导入任务taskId)', + file_name varchar(255) NOT NULL COMMENT '文件原始名称', + file_path varchar(500) NOT NULL COMMENT '存储路径(相对路径)', + file_size bigint(20) DEFAULT 0 COMMENT '文件大小(字节)', + content_type varchar(100) DEFAULT '' COMMENT 'MIME类型', + file_extension varchar(20) DEFAULT '' COMMENT '扩展名', + remark varchar(500) DEFAULT NULL COMMENT '备注', + create_by varchar(64) DEFAULT '' COMMENT '创建人', + create_time datetime DEFAULT NULL COMMENT '创建时间', + update_by varchar(64) DEFAULT '' COMMENT '更新人', + update_time datetime DEFAULT NULL COMMENT '更新时间', + del_flag char(1) DEFAULT '0' COMMENT '删除标志(0存在 2删除)', + PRIMARY KEY (attachment_id), + KEY idx_biz_attach_type_time (biz_type, create_time), + KEY idx_biz_attach_biz_id (biz_type, biz_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公共业务附件'; diff --git a/bms-front/src/api/price/billing.js b/bms-front/src/api/price/billing.js index 41e2c56a..91249fd1 100644 --- a/bms-front/src/api/price/billing.js +++ b/bms-front/src/api/price/billing.js @@ -277,3 +277,12 @@ export async function downloadChargeWeightTemplate(data, filename) { loading.close() } } + +/** 批量修改计费重导入附件记录 */ +export function listChargeWeightImportAttachments(query) { + return request({ + url: '/price/billing/bill/chargeWeight/import/attachments', + method: 'get', + params: query + }) +} diff --git a/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncLogDrawer.vue b/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncLogDrawer.vue index c5208f66..55159f3e 100644 --- a/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncLogDrawer.vue +++ b/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncLogDrawer.vue @@ -35,6 +35,14 @@ {{ detail.errorMessage }} +
+
失败明细(共 {{ failRows.length }} 单)
+ + + + + +
请求报文
{{ formatJson(detail.requestJson) }}
@@ -74,7 +82,7 @@ const searchColumns = [ ] const columns = [ - { label: '操作', prop: 'actionName', width: 120, slot: 'actionName' }, + { label: '操作', prop: 'actionName', width: 150, slot: 'actionName' }, { label: '业务ID', prop: 'bizId', minWidth: 180, showOverflowTooltip: true }, { label: '状态', prop: 'status', align: 'center', width: 90, slot: 'status' }, { label: '写入时间', prop: 'updateTime', align: 'center', width: 170 }, @@ -93,12 +101,41 @@ function parseReq(row) { function actionLabel(row) { const action = parseReq(row).action if (action === 'FILTER') return '筛选同步' - if (action === 'FILTER_DONE') return '筛选完成' - if (action === 'SELECTED') return '同步选中' + if (action === 'FILTER_DONE') { + return row.status === 'FAIL' ? '筛选完成(有失败)' : '筛选完成' + } + if (action === 'SELECTED') { + return row.status === 'FAIL' ? '同步选中(有失败)' : '同步选中' + } if (action === 'ONE') return '单条同步' + if (action === 'ONE_FAIL') return '同步失败' return action || '-' } +const failRows = computed(() => { + if (!detail.value) return [] + const resp = parseResp(detail.value) + if (Array.isArray(resp.failDetails) && resp.failDetails.length) { + return resp.failDetails + } + if (Array.isArray(resp.details) && resp.details.length) { + return resp.details.filter(d => { + const st = d && d.successStatus + return st && String(st).toUpperCase() !== 'SUCCESS' + }) + } + return [] +}) + +function parseResp(row) { + if (!row || !row.responseJson) return {} + try { + return typeof row.responseJson === 'object' ? row.responseJson : JSON.parse(row.responseJson) + } catch (e) { + return {} + } +} + function queryList(params) { return listOrderSourceSyncLog(params) } @@ -143,6 +180,9 @@ defineExpose({ open }) margin-bottom: 6px; font-weight: 600; } +.fail-table { + margin-top: 6px; +} .code-pre { margin: 0; padding: 10px 12px; diff --git a/bms-front/src/views/price/billing/bill.vue b/bms-front/src/views/price/billing/bill.vue index 30980461..d61f3229 100644 --- a/bms-front/src/views/price/billing/bill.vue +++ b/bms-front/src/views/price/billing/bill.vue @@ -95,6 +95,15 @@ @click="openChargeWeightImport" >批量修改计费重 + + 导入记录 + + + + + + + + + + +