提交 0eb34fe9 authored 作者: 窦馨雨's avatar 窦馨雨

合并分支 'dxy' 到 'qa'

增加店内执行计划导出/入功能 查看合并请求 !95
...@@ -54,7 +54,7 @@ public class OssConfigProperties{ ...@@ -54,7 +54,7 @@ public class OssConfigProperties{
/** /**
* 静态网站访问地址前缀 * 静态网站访问地址前缀
*/ */
private static final String END_POINT_PREFIX = "https://"; private static final String END_POINT_PREFIX = "https://oss-";
/** /**
* 静态网站访问地址后缀 * 静态网站访问地址后缀
......
...@@ -69,6 +69,7 @@ public class SalesApRequest { ...@@ -69,6 +69,7 @@ public class SalesApRequest {
* 3.零食陈列 :SNACK_DISPLAY_EXPORT * 3.零食陈列 :SNACK_DISPLAY_EXPORT
* 4.三米两秒 :THREE_METER_TWO_SECONDS_EXPORT * 4.三米两秒 :THREE_METER_TWO_SECONDS_EXPORT
* 5.六小金刚 :SIX_KINGkONG_EXPORT * 5.六小金刚 :SIX_KINGkONG_EXPORT
* 6.档期陈列 : PROMOTION_DISPLAY_EXPORT
*/ */
private String pageType; private String pageType;
......
...@@ -11,9 +11,6 @@ import com.aliyuncs.exceptions.ClientException; ...@@ -11,9 +11,6 @@ import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType; import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile; import com.aliyuncs.profile.IClientProfile;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.sfa.operation.config.ExportColumnConfig; import com.sfa.operation.config.ExportColumnConfig;
import com.sfa.operation.config.OssConfigProperties; import com.sfa.operation.config.OssConfigProperties;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -31,6 +28,8 @@ import javax.annotation.PostConstruct; ...@@ -31,6 +28,8 @@ import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.*; import java.io.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
...@@ -61,46 +60,46 @@ public class ExcelUtils { ...@@ -61,46 +60,46 @@ public class ExcelUtils {
@Autowired @Autowired
private OssConfigProperties ossConfig; private OssConfigProperties ossConfig;
public static String extractFileNameFromUrl(String filePathUrl) { // public static String extractFileNameFromUrl(String filePathUrl) {
if (StringUtils.isBlank(filePathUrl)) { // if (StringUtils.isBlank(filePathUrl)) {
return "未知文件"; // return "未知文件";
} // }
try { // try {
// 先尝试URL解码,处理包含特殊字符或中文的文件名 // // 先尝试URL解码,处理包含特殊字符或中文的文件名
String decodedUrl = URLDecoder.decode(filePathUrl, StandardCharsets.UTF_8.name()); // String decodedUrl = URLDecoder.decode(filePathUrl, StandardCharsets.UTF_8.name());
//
// 方案1:标准文件路径(file:///C:/Users/xxx/Desktop/data.xlsx) // // 方案1:标准文件路径(file:///C:/Users/xxx/Desktop/data.xlsx)
if (decodedUrl.startsWith("file:")) { // if (decodedUrl.startsWith("file:")) {
return new File(decodedUrl.substring(6)).getName(); // return new File(decodedUrl.substring(6)).getName();
} // }
//
// 方案2:普通绝对/相对路径(如"C:\\Users\\xxx\\Desktop\\data.xlsx"或"./data.xlsx") // // 方案2:普通绝对/相对路径(如"C:\\Users\\xxx\\Desktop\\data.xlsx"或"./data.xlsx")
if (decodedUrl.contains(".") || decodedUrl.contains("/")) { // if (decodedUrl.contains(".") || decodedUrl.contains("/")) {
return new File(decodedUrl).getName(); // return new File(decodedUrl).getName();
} // }
//
// 方案3:URL路径(如"http://example.com/path/data.xlsx?version=1") // // 方案3:URL路径(如"http://example.com/path/data.xlsx?version=1")
if (decodedUrl.contains("://")) { // if (decodedUrl.contains("://")) {
String pathPart = decodedUrl.substring(decodedUrl.indexOf("://") + 3); // String pathPart = decodedUrl.substring(decodedUrl.indexOf("://") + 3);
if (pathPart.contains("/")) { // if (pathPart.contains("/")) {
pathPart = pathPart.substring(pathPart.indexOf("/") + 1); // pathPart = pathPart.substring(pathPart.indexOf("/") + 1);
} // }
// 移除查询参数(?之后的部分) // // 移除查询参数(?之后的部分)
if (pathPart.contains("?")) { // if (pathPart.contains("?")) {
pathPart = pathPart.substring(0, pathPart.indexOf("?")); // pathPart = pathPart.substring(0, pathPart.indexOf("?"));
} // }
return new File(pathPart).getName(); // return new File(pathPart).getName();
} // }
//
// 默认兜底方案:直接当作文件名处理 // // 默认兜底方案:直接当作文件名处理
return decodedUrl; // return decodedUrl;
} catch (Exception e) { // } catch (Exception e) {
log.warn("解析文件名异常,使用原始名称:{},错误:{}", filePathUrl, e.getMessage()); // log.warn("解析文件名异常,使用原始名称:{},错误:{}", filePathUrl, e.getMessage());
return filePathUrl; // return filePathUrl;
} // }
//
//
} // }
@PostConstruct @PostConstruct
public void init() { public void init() {
...@@ -174,31 +173,43 @@ public class ExcelUtils { ...@@ -174,31 +173,43 @@ public class ExcelUtils {
//用STS临时凭证创建OSS客户端 //用STS临时凭证创建OSS客户端
OSS ossClient = null; OSS ossClient = null;
InputStream ossInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try { try {
// Endpoint
ossClient = new OSSClientBuilder().build( ossClient = new OSSClientBuilder().build(
"https://oss-" + oss.getRegionId() + ".aliyuncs.com", oss.getEndPoint(),
// STS临时AK
stsCredentials.getAccessKeyId(), stsCredentials.getAccessKeyId(),
// STS临时SK
stsCredentials.getAccessKeySecret(), stsCredentials.getAccessKeySecret(),
// STS Token
stsCredentials.getSecurityToken() stsCredentials.getSecurityToken()
); );
// 校验文件是否存在
if (!ossClient.doesObjectExist(oss.getBucketName(), objectKey)) { if (!ossClient.doesObjectExist(oss.getBucketName(), objectKey)) {
throw new Exception(String.format("OSS文件不存在:bucket=%s, path=%s", oss.getBucketName(), objectKey)); throw new Exception(String.format("OSS文件不存在:bucket=%s, path=%s", oss.getBucketName(), objectKey));
} }
// 获取输入流(调用时注意关闭流) // 读取OSS流到内存
return ossClient.getObject(new GetObjectRequest(oss.getBucketName(), objectKey)).getObjectContent(); ossInputStream = ossClient.getObject(new GetObjectRequest(oss.getBucketName(), objectKey)).getObjectContent();
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int len;
while ((len = ossInputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
} catch (Exception e) { } catch (Exception e) {
log.error("获取OSS文件失败:url={}", ossFileUrl, e); log.error("获取OSS文件失败:url={}", ossFileUrl, e);
throw new Exception("OSS文件读取失败:" + e.getMessage(), e); throw new Exception("OSS文件读取失败:" + e.getMessage(), e);
} finally { } finally {
// 核心修改2:调整关闭顺序
if (ossInputStream != null) {
try { ossInputStream.close(); } catch (IOException e) { log.warn("关闭OSS流失败", e); }
}
if (byteArrayOutputStream != null) {
try { byteArrayOutputStream.close(); } catch (IOException e) { log.warn("关闭内存流失败", e); }
}
if (ossClient != null) { if (ossClient != null) {
// 关闭客户端
ossClient.shutdown(); ossClient.shutdown();
} }
} }
...@@ -339,20 +350,20 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -339,20 +350,20 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
Workbook workbook = null; Workbook workbook = null;
try { try {
// 1. 校验文件格式 //校验文件格式
if (fileName == null || (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx"))) { if (fileName == null || (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx"))) {
errorList.add("文件格式错误,仅支持.xls和.xlsx"); errorList.add("文件格式错误,仅支持.xls和.xlsx");
return dataList; return dataList;
} }
// 2. 创建Workbook // 创建Workbook
if (fileName.endsWith(".xls")) { if (fileName.endsWith(".xls")) {
workbook = new HSSFWorkbook(inputStream); workbook = new HSSFWorkbook(inputStream);
} else { } else {
workbook = new XSSFWorkbook(inputStream); workbook = new XSSFWorkbook(inputStream);
} }
// 3. 校验Sheet //校验Sheet
Sheet sheet = workbook.getSheetAt(0); Sheet sheet = workbook.getSheetAt(0);
if (sheet == null) { if (sheet == null) {
errorList.add("获取sheet为空,导入失败,请检查文件!"); errorList.add("获取sheet为空,导入失败,请检查文件!");
...@@ -364,11 +375,11 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -364,11 +375,11 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
return dataList; return dataList;
} }
// 4. 逐行解析 // 逐行解析
for (int rowIndex = HEADER_ROW + 1; rowIndex <= lastRowNum; rowIndex++) { for (int rowIndex = HEADER_ROW + 1; rowIndex <= lastRowNum; rowIndex++) {
Row row = sheet.getRow(rowIndex); Row row = sheet.getRow(rowIndex);
if (row == null) { if (row == null) {
// 调整:移除行号,仅保留纯错误提示 // 移除行号,仅保留纯错误提示
errorList.add("数据行为空,跳过处理"); errorList.add("数据行为空,跳过处理");
continue; continue;
} }
...@@ -381,7 +392,7 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -381,7 +392,7 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
log.error("导入Excel文件失败:{}", e.getMessage(), e); log.error("导入Excel文件失败:{}", e.getMessage(), e);
errorList.add("文件读取失败:" + e.getMessage()); errorList.add("文件读取失败:" + e.getMessage());
} finally { } finally {
// 5. 关闭流 // 关闭流
try { try {
if (workbook != null) { if (workbook != null) {
workbook.close(); workbook.close();
...@@ -920,6 +931,7 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -920,6 +931,7 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
request.setRoleArn(oss.getStsRoleArm()); request.setRoleArn(oss.getStsRoleArm());
request.setRoleSessionName(oss.getSessionName()); request.setRoleSessionName(oss.getSessionName());
request.setDurationSeconds(3600L);
// 获取STS响应 // 获取STS响应
AssumeRoleResponse response = client.getAcsResponse(request); AssumeRoleResponse response = client.getAcsResponse(request);
AssumeRoleResponse.Credentials credentials = response.getCredentials(); AssumeRoleResponse.Credentials credentials = response.getCredentials();
...@@ -933,22 +945,80 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -933,22 +945,80 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
/** /**
* 从OSS文件URL中解析出ObjectKey * 从OSS文件URL中解析出ObjectKey
* @param ossFileUrl OSS文件URL * @param ossFileUrl OSS文件URL
* @param ossDomain OSS域名 * @param webJsLink OSS域名
* @return ObjectKey * @return ObjectKey
*/ */
private static String parseObjectKeyFromUrl(String ossFileUrl, String ossDomain) { private static String parseObjectKeyFromUrl(String ossFileUrl, String webJsLink) {
if (StringUtils.isBlank(ossFileUrl) || StringUtils.isBlank(ossDomain)) { if (StringUtils.isBlank(ossFileUrl) || StringUtils.isBlank(webJsLink)) {
log.error("解析OSS ObjectKey失败:URL或域名为空,url={}, domain={}", ossFileUrl, webJsLink);
return null; return null;
} }
String domainPrefix = "//" + ossDomain + "/";
if (ossFileUrl.contains(domainPrefix)) { try {
return ossFileUrl.split(domainPrefix)[1]; // 步骤1:提取配置域名的「纯域名」(忽略协议)
} String targetDomain = extractDomainWithoutProtocol(webJsLink);
// 兼容直接传入objectKey的场景(如path/file.xlsx) if (StringUtils.isBlank(targetDomain)) {
if (!ossFileUrl.startsWith("http")) { log.error("解析OSS ObjectKey失败:目标域名提取为空,webJsLink={}", webJsLink);
return ossFileUrl; return null;
}
// 步骤2:解析OSS URL,提取域名和路径
URL url = new URL(ossFileUrl);
// URL的纯域名
String urlDomain = extractDomainWithoutProtocol(url.getHost());
// URL的路径部分
String urlPath = url.getPath();
// 步骤3:校验域名是否匹配
if (!targetDomain.equals(urlDomain)) {
log.error("解析OSS ObjectKey失败:URL域名不匹配,urlDomain={}, targetDomain={}", urlDomain, targetDomain);
return null;
}
// 步骤4:提取ObjectKey(去掉路径开头的/、剥离查询参数)
String objectKey = urlPath.startsWith("/") ? urlPath.substring(1) : urlPath;
int queryIndex = objectKey.indexOf("?");
if (queryIndex > 0) {
objectKey = objectKey.substring(0, queryIndex);
}
// 步骤5:URL解码(处理中文/特殊字符)
objectKey = URLDecoder.decode(objectKey, StandardCharsets.UTF_8.name());
if (StringUtils.isBlank(objectKey)) {
log.error("解析OSS ObjectKey失败:解析后路径为空,url={}", ossFileUrl);
return null;
}
log.info("解析OSS ObjectKey成功:url={} → objectKey={}", ossFileUrl, objectKey);
return objectKey;
} catch (MalformedURLException e) {
// 兼容「直接传ObjectKey(非URL)」的场景
if (!ossFileUrl.startsWith("http")) {
String objectKey = ossFileUrl;
// 剥离查询参数
int queryIndex = objectKey.indexOf("?");
if (queryIndex > 0) {
objectKey = objectKey.substring(0, queryIndex);
}
// URL解码
try {
objectKey = URLDecoder.decode(objectKey, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException ex) {
log.error("URL解码失败", ex);
}
return objectKey;
}
log.error("解析OSS ObjectKey异常:URL格式错误,url={}", ossFileUrl, e);
return null;
} catch (UnsupportedEncodingException e) {
log.error("URL解码异常", e);
return null;
} catch (Exception e) {
log.error("解析OSS ObjectKey异常:url={}, domain={}", ossFileUrl, webJsLink, e);
return null;
} }
return null;
} }
/** /**
...@@ -964,21 +1034,54 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream, ...@@ -964,21 +1034,54 @@ public static <T> List<T> readApExcelWithColumnConfig(InputStream inputStream,
return "Excel导入文件"; return "Excel导入文件";
} }
// 解析文件名:按"/"分割取URL最后一段 try {
String[] urlParts = ossUrl.split("/"); // 先解析ObjectKey,复用已有的解析逻辑
String fileName = urlParts[urlParts.length - 1]; OssConfigProperties.Oss oss = staticOssConfig.getOss();
String objectKey = parseObjectKeyFromUrl(ossUrl, oss.getWebJsLink());
if (StringUtils.isBlank(objectKey)) {
String[] urlParts = ossUrl.split("/");
objectKey = urlParts[urlParts.length - 1];
}
// 提取文件名(按/分割取最后一段)
String[] pathParts = objectKey.split("/");
String fileName = pathParts[pathParts.length - 1];
// 步骤:URL解码(解决中文文件名乱码)
fileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8.name());
// - 解析结果为空 → 通用前缀+时间戳+默认后缀
// - 无Excel后缀 → 补充.xlsx(适配Excel导入通用场景)
if (StringUtils.isBlank(fileName)) {
fileName = "Excel导入文件_" + System.currentTimeMillis() + ".xlsx";
} else if (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) {
// 保留原文件名前缀 + 补充Excel后缀(更友好)
fileName = fileName + ".xlsx";
}
// - 解析结果为空 → 通用前缀+时间戳+默认后缀 log.info("从OSS URL解析通用文件名完成:{} → {}", ossUrl, fileName);
// - 无Excel后缀 → 补充.xlsx(适配Excel导入通用场景) return fileName;
if (StringUtils.isBlank(fileName)) { } catch (Exception e) {
fileName = "Excel导入文件_" + System.currentTimeMillis() + ".xlsx"; log.error("解析OSS文件名异常:url={}", ossUrl, e);
} else if (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) { // 兜底逻辑
// 保留原文件名前缀 + 补充Excel后缀(更友好) return "Excel导入文件_" + System.currentTimeMillis() + ".xlsx";
fileName = fileName + ".xlsx";
} }
}
log.info("从OSS URL解析通用文件名完成:{} → {}", ossUrl, fileName); private static String extractDomainWithoutProtocol(String urlOrDomain) {
return fileName; if (StringUtils.isBlank(urlOrDomain)) {
return "";
}
// 1. 去掉http/https协议头
String domain = urlOrDomain.replaceFirst("^https?://", "");
// 2. 去掉端口(如:8080)
int portIndex = domain.indexOf(":");
if (portIndex != -1) {
domain = domain.substring(0, portIndex);
}
// 3. 去掉末尾的/
domain = domain.replaceFirst("/+$", "");
return domain.trim().toLowerCase();
} }
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论