This commit is contained in:
van
2026-03-24 15:36:52 +08:00
parent 175cd3ba01
commit 318cef274e
8 changed files with 387 additions and 25 deletions

View File

@@ -1,14 +1,20 @@
package com.ruoyi.web.controller.system;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import com.ruoyi.jarvis.domain.OrderRows;
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
import com.ruoyi.jarvis.service.impl.GroupRebateExcelImportService;
import com.ruoyi.jarvis.service.IOrderRowsService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
@@ -37,14 +43,17 @@ public class JDOrderListController extends BaseController
private final IOrderRowsService orderRowsService;
private final IInstructionService instructionService;
private final GroupRebateExcelImportService groupRebateExcelImportService;
private final IGroupRebateExcelUploadService groupRebateExcelUploadService;
public JDOrderListController(IJDOrderService jdOrderService, IOrderRowsService orderRowsService,
IInstructionService instructionService,
GroupRebateExcelImportService groupRebateExcelImportService) {
GroupRebateExcelImportService groupRebateExcelImportService,
IGroupRebateExcelUploadService groupRebateExcelUploadService) {
this.jdOrderService = jdOrderService;
this.orderRowsService = orderRowsService;
this.instructionService = instructionService;
this.groupRebateExcelImportService = groupRebateExcelImportService;
this.groupRebateExcelUploadService = groupRebateExcelUploadService;
}
/**
@@ -140,10 +149,7 @@ public class JDOrderListController extends BaseController
}
/**
* 导出JD订单列表
*/
/**
* 导入跟团返现类 Excel按「单号/订单号」匹配系统订单,将「是否返现」「总共返现」等写入后返备注(可多次导入累加)
* 导入跟团返现类 Excel按「单号/订单号」匹配系统订单,将「是否返现」「总共返现」等写入后返备注(可多次导入累加);文件落盘并记上传记录。
*/
@Log(title = "JD订单后返表导入", businessType = BusinessType.IMPORT)
@PostMapping("/importGroupRebateExcel")
@@ -151,15 +157,55 @@ public class JDOrderListController extends BaseController
@RequestParam(value = "documentTitle", required = false) String documentTitle) {
try {
Map<String, Object> data = groupRebateExcelImportService.importExcel(file, documentTitle);
if (Boolean.FALSE.equals(data.get("success"))) {
return AjaxResult.error(String.valueOf(data.get("message")));
}
return AjaxResult.success(data);
} catch (Exception e) {
return AjaxResult.error("导入失败: " + e.getMessage());
}
}
/**
* 后返表上传记录(分页)
*/
@GetMapping("/groupRebateUpload/list")
public TableDataInfo groupRebateUploadList(GroupRebateExcelUpload query, HttpServletRequest request) {
startPage();
String beginTimeStr = request.getParameter("beginTime");
String endTimeStr = request.getParameter("endTime");
if (beginTimeStr != null && !beginTimeStr.isEmpty()) {
query.getParams().put("beginTime", beginTimeStr);
}
if (endTimeStr != null && !endTimeStr.isEmpty()) {
query.getParams().put("endTime", endTimeStr);
}
List<GroupRebateExcelUpload> list = groupRebateExcelUploadService.selectGroupRebateExcelUploadList(query);
return getDataTable(list);
}
/**
* 重新下载已上传的后返表原件(与通用 download 一致,使用 POST + blob
*/
@Log(title = "后返表上传记录下载", businessType = BusinessType.EXPORT)
@PostMapping("/groupRebateUpload/download/{id}")
public void downloadGroupRebateUpload(@PathVariable("id") Long id, HttpServletResponse response) throws Exception {
GroupRebateExcelUpload rec = groupRebateExcelUploadService.selectGroupRebateExcelUploadById(id);
if (rec == null || StringUtils.isEmpty(rec.getFilePath())) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
File file = GroupRebateExcelImportService.resolveDiskFile(rec.getFilePath());
if (!file.isFile()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
String downloadName = StringUtils.isNotEmpty(rec.getOriginalFilename()) ? rec.getOriginalFilename() : file.getName();
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, downloadName);
FileUtils.writeBytes(file.getAbsolutePath(), response.getOutputStream());
}
/**
* 导出JD订单列表
*/
@Log(title = "JD订单", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, JDOrder jdOrder) throws IOException

View File

@@ -0,0 +1,28 @@
package com.ruoyi.jarvis.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 后返/跟团返现 Excel 上传记录
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GroupRebateExcelUpload extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String documentTitle;
private String originalFilename;
/** 若依资源路径,如 /profile/upload/group-rebate-excel/... */
private String filePath;
private Long fileSize;
/** 1 解析失败或未处理 2 已成功解析并写订单 */
private Integer importStatus;
private Integer dataRows;
private Integer updatedOrders;
private Integer notFoundCount;
private String resultDetailJson;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.jarvis.mapper;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import java.util.List;
public interface GroupRebateExcelUploadMapper {
int insertGroupRebateExcelUpload(GroupRebateExcelUpload row);
GroupRebateExcelUpload selectGroupRebateExcelUploadById(Long id);
List<GroupRebateExcelUpload> selectGroupRebateExcelUploadList(GroupRebateExcelUpload query);
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.jarvis.service;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import java.util.List;
public interface IGroupRebateExcelUploadService {
/**
* 独立事务写入上传记录(导入主流程回滚时仍保留审计记录)
*/
void saveRecordInNewTransaction(GroupRebateExcelUpload row);
GroupRebateExcelUpload selectGroupRebateExcelUploadById(Long id);
List<GroupRebateExcelUpload> selectGroupRebateExcelUploadList(GroupRebateExcelUpload query);
}

View File

@@ -1,9 +1,17 @@
package com.ruoyi.jarvis.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.domain.dto.RebateRemarkItem;
import com.ruoyi.jarvis.mapper.JDOrderMapper;
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.DateUtil;
@@ -13,11 +21,15 @@ import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
@@ -30,7 +42,7 @@ import java.util.Set;
import java.util.regex.Pattern;
/**
* 解析跟团返现类 Excel表头含单号、是否返现、返现金额等写入订单后返备注 JSON。
* 解析跟团返现类 Excel表头含单号、是否返现、返现金额等写入订单后返备注 JSON;并保存上传文件与数据库记录
*/
@Service
public class GroupRebateExcelImportService {
@@ -39,6 +51,8 @@ public class GroupRebateExcelImportService {
private static final DataFormatter DATA_FORMATTER = new DataFormatter();
private static final String[] EXCEL_EXT = { "xls", "xlsx" };
private static final Pattern ABNORMAL_HINT = Pattern.compile(
".*(异常|待补|待返|下次|驳回|未返|不返|暂缓|缺|补表|重做|暂无|无$|-).*", Pattern.CASE_INSENSITIVE);
@@ -48,29 +62,73 @@ public class GroupRebateExcelImportService {
@Resource
private JDOrderMapper jdOrderMapper;
@Transactional(rollbackFor = Exception.class)
@Resource
private IGroupRebateExcelUploadService groupRebateExcelUploadService;
private GroupRebateExcelImportService self;
@Autowired
public void setSelf(@Lazy GroupRebateExcelImportService self) {
this.self = self;
}
/**
* 先落盘上传文件,再解析写订单;无论成败均写入上传记录(独立事务)。
*/
public Map<String, Object> importExcel(MultipartFile file, String documentTitle) throws Exception {
Map<String, Object> result = new HashMap<>();
String originalFilename = file != null ? file.getOriginalFilename() : null;
long fileSize = file != null ? file.getSize() : 0L;
String title = resolveDocumentTitle(file, documentTitle);
String webPath = "";
if (file == null || file.isEmpty()) {
result.put("success", false);
result.put("message", "请选择文件");
return result;
}
String title = documentTitle != null ? documentTitle.trim() : "";
if (title.isEmpty()) {
String name = file.getOriginalFilename();
if (name != null && name.contains(".")) {
title = name.substring(0, name.lastIndexOf('.'));
} else {
title = name != null ? name : "未命名文档";
}
Map<String, Object> empty = new HashMap<>();
empty.put("success", false);
empty.put("message", "请选择文件");
empty.put("documentTitle", title);
persistUploadRecord(originalFilename, webPath, fileSize, title, empty);
return empty;
}
try (InputStream is = file.getInputStream(); Workbook wb = WorkbookFactory.create(is)) {
try {
String baseDir = RuoYiConfig.getUploadPath() + File.separator + "group-rebate-excel";
webPath = FileUploadUtils.upload(baseDir, file, EXCEL_EXT);
} catch (Exception e) {
log.warn("保存后返Excel失败", e);
Map<String, Object> fail = new HashMap<>();
fail.put("success", false);
fail.put("message", "保存上传文件失败: " + e.getMessage());
fail.put("documentTitle", title);
persistUploadRecord(originalFilename, webPath, fileSize, title, fail);
return fail;
}
File diskFile = resolveDiskFile(webPath);
Map<String, Object> result;
try {
result = self.processWorkbookTransactional(diskFile, title);
} catch (Exception e) {
log.error("后返表导入处理异常", e);
result = new HashMap<>();
result.put("success", false);
result.put("message", "导入处理异常: " + e.getMessage());
result.put("documentTitle", title);
}
result.put("savedFilePath", webPath);
Long recordId = persistUploadRecord(originalFilename, webPath, fileSize, title, result);
result.put("uploadRecordId", recordId);
return result;
}
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> processWorkbookTransactional(File diskFile, String title) throws Exception {
Map<String, Object> result = new HashMap<>();
try (InputStream is = new FileInputStream(diskFile); Workbook wb = WorkbookFactory.create(is)) {
Sheet sheet = wb.getNumberOfSheets() > 0 ? wb.getSheetAt(0) : null;
if (sheet == null) {
result.put("success", false);
result.put("message", "工作簿无工作表");
result.put("documentTitle", title);
return result;
}
@@ -78,6 +136,7 @@ public class GroupRebateExcelImportService {
if (headerRowIndex < 0) {
result.put("success", false);
result.put("message", "未找到表头(需包含「单号」或「订单号」列,以及「是否返现」或「总共返现」相关列)");
result.put("documentTitle", title);
return result;
}
@@ -86,11 +145,13 @@ public class GroupRebateExcelImportService {
if (hm.orderCol < 0) {
result.put("success", false);
result.put("message", "未识别订单号列(表头需含「单号」或「订单号」,不含「第三方」)");
result.put("documentTitle", title);
return result;
}
if (hm.whetherRebateCol < 0 && hm.totalCashbackCol < 0) {
result.put("success", false);
result.put("message", "未识别「是否返现」或返现金额列(如「总共返现」)");
result.put("documentTitle", title);
return result;
}
@@ -154,6 +215,90 @@ public class GroupRebateExcelImportService {
}
}
private Long persistUploadRecord(String originalFilename, String webPath, long fileSize,
String title, Map<String, Object> procResult) {
GroupRebateExcelUpload row = new GroupRebateExcelUpload();
row.setOriginalFilename(originalFilename);
row.setFilePath(StringUtils.isNotEmpty(webPath) ? webPath : "");
row.setFileSize(fileSize);
row.setDocumentTitle(title);
try {
row.setCreateBy(SecurityUtils.getUsername());
} catch (Exception e) {
row.setCreateBy("");
}
boolean ok = Boolean.TRUE.equals(procResult.get("success"));
row.setImportStatus(ok ? 2 : 1);
row.setDataRows(asInt(procResult.get("dataRows")));
row.setUpdatedOrders(asInt(procResult.get("updatedOrders")));
@SuppressWarnings("unchecked")
List<String> nf = (List<String>) procResult.get("notFoundOrderNos");
row.setNotFoundCount(nf != null ? nf.size() : null);
JSONObject detail = new JSONObject();
detail.put("message", procResult.get("message"));
detail.put("documentTitle", procResult.get("documentTitle"));
if (procResult.containsKey("notFoundOrderNos")) {
detail.put("notFoundOrderNos", procResult.get("notFoundOrderNos"));
}
if (procResult.containsKey("errors")) {
detail.put("errors", procResult.get("errors"));
}
if (procResult.containsKey("savedFilePath")) {
detail.put("savedFilePath", procResult.get("savedFilePath"));
}
row.setResultDetailJson(detail.toJSONString());
try {
groupRebateExcelUploadService.saveRecordInNewTransaction(row);
return row.getId();
} catch (Exception e) {
log.error("写入后返上传记录失败", e);
return null;
}
}
private static Integer asInt(Object o) {
if (o == null) {
return null;
}
if (o instanceof Number) {
return ((Number) o).intValue();
}
try {
return Integer.parseInt(o.toString());
} catch (NumberFormatException e) {
return null;
}
}
private static String resolveDocumentTitle(MultipartFile file, String documentTitle) {
String t = documentTitle != null ? documentTitle.trim() : "";
if (!t.isEmpty()) {
return t;
}
if (file == null) {
return "未命名文档";
}
String name = file.getOriginalFilename();
if (name != null && name.contains(".")) {
return name.substring(0, name.lastIndexOf('.'));
}
return name != null ? name : "未命名文档";
}
/** 将 /profile/... 转为本地 File */
public static File resolveDiskFile(String webPath) {
if (StringUtils.isEmpty(webPath)) {
throw new IllegalArgumentException("file path empty");
}
String sub = StringUtils.substringAfter(webPath, Constants.RESOURCE_PREFIX);
if (sub.startsWith("/")) {
sub = sub.substring(1);
}
return new File(RuoYiConfig.getProfile(), sub);
}
private void appendRebateRemark(JDOrder order, String documentTitle, String whetherRebate, String rebateAmount) {
List<RebateRemarkItem> list = new ArrayList<>();
String existing = order.getRebateRemarkJson();
@@ -342,7 +487,6 @@ public class GroupRebateExcelImportService {
int orderCol = -1;
int whetherRebateCol = -1;
int totalCashbackCol = -1;
/** 无「总共返现」时的备选:含「返现」「金额」的列 */
int fallbackAmountCol = -1;
}
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.jarvis.service.impl;
import com.ruoyi.jarvis.domain.GroupRebateExcelUpload;
import com.ruoyi.jarvis.mapper.GroupRebateExcelUploadMapper;
import com.ruoyi.jarvis.service.IGroupRebateExcelUploadService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
public class GroupRebateExcelUploadServiceImpl implements IGroupRebateExcelUploadService {
@Resource
private GroupRebateExcelUploadMapper groupRebateExcelUploadMapper;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void saveRecordInNewTransaction(GroupRebateExcelUpload row) {
groupRebateExcelUploadMapper.insertGroupRebateExcelUpload(row);
}
@Override
public GroupRebateExcelUpload selectGroupRebateExcelUploadById(Long id) {
return groupRebateExcelUploadMapper.selectGroupRebateExcelUploadById(id);
}
@Override
public List<GroupRebateExcelUpload> selectGroupRebateExcelUploadList(GroupRebateExcelUpload query) {
return groupRebateExcelUploadMapper.selectGroupRebateExcelUploadList(query);
}
}

View File

@@ -0,0 +1,62 @@
<?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="com.ruoyi.jarvis.mapper.GroupRebateExcelUploadMapper">
<resultMap id="GroupRebateExcelUploadResult" type="GroupRebateExcelUpload">
<id property="id" column="id"/>
<result property="documentTitle" column="document_title"/>
<result property="originalFilename" column="original_filename"/>
<result property="filePath" column="file_path"/>
<result property="fileSize" column="file_size"/>
<result property="importStatus" column="import_status"/>
<result property="dataRows" column="data_rows"/>
<result property="updatedOrders" column="updated_orders"/>
<result property="notFoundCount" column="not_found_count"/>
<result property="resultDetailJson" column="result_detail_json"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectCols">
select id, document_title, original_filename, file_path, file_size, import_status,
data_rows, updated_orders, not_found_count, result_detail_json, create_by, create_time
from jd_group_rebate_excel_upload
</sql>
<insert id="insertGroupRebateExcelUpload" parameterType="GroupRebateExcelUpload" useGeneratedKeys="true" keyProperty="id">
insert into jd_group_rebate_excel_upload (
document_title, original_filename, file_path, file_size, import_status,
data_rows, updated_orders, not_found_count, result_detail_json, create_by, create_time
) values (
#{documentTitle}, #{originalFilename}, #{filePath}, #{fileSize}, #{importStatus},
#{dataRows}, #{updatedOrders}, #{notFoundCount}, #{resultDetailJson}, #{createBy}, now()
)
</insert>
<select id="selectGroupRebateExcelUploadById" parameterType="long" resultMap="GroupRebateExcelUploadResult">
<include refid="selectCols"/>
where id = #{id}
</select>
<select id="selectGroupRebateExcelUploadList" parameterType="GroupRebateExcelUpload" resultMap="GroupRebateExcelUploadResult">
<include refid="selectCols"/>
<where>
<if test="documentTitle != null and documentTitle != ''">
and document_title like concat('%', #{documentTitle}, '%')
</if>
<if test="originalFilename != null and originalFilename != ''">
and original_filename like concat('%', #{originalFilename}, '%')
</if>
<if test="importStatus != null">
and import_status = #{importStatus}
</if>
<if test="params.beginTime != null and params.beginTime != ''">
and date(create_time) &gt;= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
and date(create_time) &lt;= #{params.endTime}
</if>
</where>
order by id desc
</select>
</mapper>

View File

@@ -0,0 +1,17 @@
-- 跟团返现 / 后返 Excel 上传记录(含服务器存档路径,可再次下载)
CREATE TABLE IF NOT EXISTS jd_group_rebate_excel_upload (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
document_title VARCHAR(255) DEFAULT NULL COMMENT '文档标题(如跟团+返现 260316',
original_filename VARCHAR(255) DEFAULT NULL COMMENT '用户原始文件名',
file_path VARCHAR(512) NOT NULL DEFAULT '' COMMENT '服务器存储相对路径(若依 /profile/upload/...',
file_size BIGINT DEFAULT NULL COMMENT '字节数',
import_status TINYINT NOT NULL DEFAULT 1 COMMENT '1 未成功处理表 2 已解析并写入订单',
data_rows INT DEFAULT NULL COMMENT '有效数据行数',
updated_orders INT DEFAULT NULL COMMENT '匹配并更新订单数',
not_found_count INT DEFAULT NULL COMMENT '未找到单号条数',
result_detail_json LONGTEXT COMMENT '未匹配单号、错误等详情JSON',
create_by VARCHAR(64) DEFAULT NULL,
create_time DATETIME DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_jd_gr_upload_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后返表Excel上传记录';