Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-module-operation
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-module-operation
Commits
db2605b2
提交
db2605b2
authored
12月 15, 2025
作者:
douxy
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
增加店内执行计划导出/入功能: 1.增加导入功能
上级
5f2ada56
隐藏空白字符变更
内嵌
并排
正在显示
21 个修改的文件
包含
1621 行增加
和
48 行删除
+1621
-48
pom.xml
pom.xml
+5
-0
OssConfigProperties.java
...in/java/com/sfa/operation/config/OssConfigProperties.java
+74
-0
ApImportExcelController.java
...ation/controller/sales/excel/ApImportExcelController.java
+36
-0
ISalesApDisplayDao.java
...om/sfa/operation/domain/sales/dao/ISalesApDisplayDao.java
+6
-0
SalesApDisplayDaoImpl.java
...peration/domain/sales/dao/impl/SalesApDisplayDaoImpl.java
+50
-0
ImportApType.java
src/main/java/com/sfa/operation/enums/ImportApType.java
+37
-0
ApExportExcelStrategyFactory.java
...m/sfa/operation/factory/ApExportExcelStrategyFactory.java
+1
-4
ApImportExcelStrategyFactory.java
...m/sfa/operation/factory/ApImportExcelStrategyFactory.java
+61
-0
SalesApDisplayImportExcelDto.java
...ration/pojo/sales/excel/SalesApDisplayImportExcelDto.java
+163
-0
ImportApExcelRequest.java
...fa/operation/pojo/sales/request/ImportApExcelRequest.java
+15
-0
SalesApDisplayVo.java
...ava/com/sfa/operation/pojo/sales/vo/SalesApDisplayVo.java
+87
-0
IApDisplayCoreService.java
...om/sfa/operation/service/sales/IApDisplayCoreService.java
+5
-0
IApDisplayQueryService.java
...m/sfa/operation/service/sales/IApDisplayQueryService.java
+3
-1
IImportExcelService.java
...a/operation/service/sales/export/IImportExcelService.java
+16
-0
ImportExcelServiceImpl.java
...ion/service/sales/export/impl/ImportExcelServiceImpl.java
+114
-0
ApDisplayCoreServiceImpl.java
...peration/service/sales/impl/ApDisplayCoreServiceImpl.java
+14
-0
ApDisplayQueryServiceImpl.java
...eration/service/sales/impl/ApDisplayQueryServiceImpl.java
+6
-7
IImportApExcelStrategy.java
...va/com/sfa/operation/strategy/IImportApExcelStrategy.java
+69
-0
NormalDisplayExportStrategyImpl.java
...trategy/impl/exports/NormalDisplayExportStrategyImpl.java
+2
-15
NormalDisplayImportStrategyImpl.java
...trategy/impl/imports/NormalDisplayImportStrategyImpl.java
+366
-0
ExcelUtils.java
src/main/java/com/sfa/operation/util/excel/ExcelUtils.java
+491
-21
没有找到文件。
pom.xml
浏览文件 @
db2605b2
...
@@ -94,6 +94,11 @@
...
@@ -94,6 +94,11 @@
<groupId>
com.taobao
</groupId>
<groupId>
com.taobao
</groupId>
<artifactId>
taobao-sdk-java
</artifactId>
<artifactId>
taobao-sdk-java
</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>
com.aliyun.oss
</groupId>
<artifactId>
aliyun-sdk-oss
</artifactId>
</dependency>
</dependencies>
</dependencies>
<build>
<build>
...
...
src/main/java/com/sfa/operation/config/OssConfigProperties.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
config
;
import
lombok.Data
;
import
org.springframework.boot.context.properties.ConfigurationProperties
;
import
org.springframework.stereotype.Component
;
/**
* @Author: DouXinYu
* @Date: 2025-12-11 15:18
* @Description: OSS配置属性
*/
@Data
@Component
@ConfigurationProperties
(
prefix
=
"aliyun"
)
public
class
OssConfigProperties
{
/**
* 访问密钥ID
*/
private
String
accessKeyId
;
/**
* 访问密钥
*/
private
String
accessKeySecret
;
/**
* OSS配置属性
*/
private
Oss
oss
=
new
Oss
();
/**
* OSS其他配置项
*/
@Data
public
static
class
Oss
{
/**
* 地域ID
*/
private
String
regionId
;
/**
* 存储空间名称
*/
private
String
bucketName
;
/**
* 静态网站访问地址
*/
private
String
webJsLink
;
/**
* 角色授权
*/
private
String
stsRoleArm
;
private
String
sessionName
;
/**
* 静态网站访问地址前缀
*/
private
static
final
String
END_POINT_PREFIX
=
"https://"
;
/**
* 静态网站访问地址后缀
*/
private
static
final
String
END_POINT_SUFFIX
=
".aliyuncs.com"
;
/**
* 获取静态网站访问地址
* @return 静态网站访问地址
*/
public
String
getEndPoint
(){
return
END_POINT_PREFIX
+
this
.
getRegionId
()
+
END_POINT_SUFFIX
;
}
}
}
src/main/java/com/sfa/operation/controller/sales/excel/ApImportExcelController.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
controller
.
sales
.
excel
;
import
com.sfa.common.core.domain.R
;
import
com.sfa.operation.pojo.sales.request.ImportApExcelRequest
;
import
com.sfa.operation.service.sales.export.IImportExcelService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 15:21
* @Description: AP导入excel(填报)控制类
*/
@RestController
@RequestMapping
(
"/sales/import"
)
public
class
ApImportExcelController
{
@Autowired
private
IImportExcelService
importExcelService
;
@PostMapping
(
"/upload"
)
public
R
importApDisplayExcel
(
@RequestBody
ImportApExcelRequest
request
)
{
return
importExcelService
.
importApExcel
(
request
);
}
@PostMapping
(
"/update"
)
public
R
updateApDisplayExcel
(
@RequestBody
ImportApExcelRequest
request
)
{
return
importExcelService
.
updateApEntity
(
request
);
}
}
src/main/java/com/sfa/operation/domain/sales/dao/ISalesApDisplayDao.java
浏览文件 @
db2605b2
...
@@ -3,6 +3,7 @@ package com.sfa.operation.domain.sales.dao;
...
@@ -3,6 +3,7 @@ package com.sfa.operation.domain.sales.dao;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
com.sfa.operation.pojo.sales.response.SalesApDisplayDto
;
import
com.sfa.operation.pojo.sales.response.SalesApDisplayDto
;
import
java.util.List
;
import
java.util.List
;
...
@@ -22,4 +23,9 @@ public interface ISalesApDisplayDao {
...
@@ -22,4 +23,9 @@ public interface ISalesApDisplayDao {
Object
queryDeptAPReport
(
SalesApWq
build
);
Object
queryDeptAPReport
(
SalesApWq
build
);
List
<
SalesApDisplay
>
queryDataListByCondition
(
SalesApWq
build
);
List
<
SalesApDisplay
>
queryDataListByCondition
(
SalesApWq
build
);
List
<
SalesApDisplay
>
queryByCondition
(
List
<
SalesApDisplayImportExcelDto
>
validDtoList
);
int
batchUpdate
(
List
<
SalesApDisplay
>
updateEntityList
);
}
}
src/main/java/com/sfa/operation/domain/sales/dao/impl/SalesApDisplayDaoImpl.java
浏览文件 @
db2605b2
...
@@ -2,6 +2,7 @@ package com.sfa.operation.domain.sales.dao.impl;
...
@@ -2,6 +2,7 @@ package com.sfa.operation.domain.sales.dao.impl;
import
com.baomidou.dynamic.datasource.annotation.DS
;
import
com.baomidou.dynamic.datasource.annotation.DS
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
com.baomidou.mybatisplus.extension.plugins.pagination.Page
;
import
com.sfa.common.core.enums.ECode
;
import
com.sfa.common.core.enums.ECode
;
import
com.sfa.common.core.exception.CheckedException
;
import
com.sfa.common.core.exception.CheckedException
;
...
@@ -13,13 +14,16 @@ import com.sfa.operation.domain.sales.dao.ISalesApDisplayDao;
...
@@ -13,13 +14,16 @@ import com.sfa.operation.domain.sales.dao.ISalesApDisplayDao;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.mapper.SalesApDisplayMapper
;
import
com.sfa.operation.domain.sales.mapper.SalesApDisplayMapper
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
com.sfa.operation.pojo.sales.response.SalesApDisplayDto
;
import
com.sfa.operation.pojo.sales.response.SalesApDisplayDto
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.util.CollectionUtils
;
import
java.util.*
;
import
java.util.*
;
import
java.util.function.Consumer
;
/**
/**
* @author : liqiulin
* @author : liqiulin
...
@@ -74,6 +78,52 @@ public class SalesApDisplayDaoImpl implements ISalesApDisplayDao {
...
@@ -74,6 +78,52 @@ public class SalesApDisplayDaoImpl implements ISalesApDisplayDao {
return
apDisplayList
;
return
apDisplayList
;
}
}
@Override
public
List
<
SalesApDisplay
>
queryByCondition
(
List
<
SalesApDisplayImportExcelDto
>
validDtoList
)
{
// 构建精准匹配的QueryWrapper(每个DTO对应一组AND条件,组间OR连接)
QueryWrapper
<
SalesApDisplay
>
wrapper
=
new
QueryWrapper
<>();
// 标记是否是第一个OR条件(避免多余的前置OR)
boolean
isFirstCondition
=
true
;
for
(
SalesApDisplayImportExcelDto
dto
:
validDtoList
)
{
// 字段为空时不参与条件匹配(或根据业务要求改为IS NULL)
Long
sadId
=
dto
.
getSadId
();
String
regionName
=
StringUtils
.
trimToNull
(
dto
.
getRegionName
());
String
dealerName
=
StringUtils
.
trimToNull
(
dto
.
getDealerName
());
String
lineName
=
StringUtils
.
trimToNull
(
dto
.
getLineName
());
// 构建单DTO的AND组合条件
Consumer
<
QueryWrapper
<
SalesApDisplay
>>
singleDtoCondition
=
w
->
{
w
.
eq
(
sadId
!=
null
,
"sad_id"
,
sadId
)
.
eq
(
regionName
!=
null
,
"region_name"
,
regionName
)
.
eq
(
dealerName
!=
null
,
"dealer_name"
,
dealerName
)
.
eq
(
lineName
!=
null
,
"line_name"
,
lineName
);
};
// 多条件OR拼接(第一个条件不加OR,避免SQL语法错误)
if
(
isFirstCondition
)
{
singleDtoCondition
.
accept
(
wrapper
);
isFirstCondition
=
false
;
}
else
{
wrapper
.
or
(
singleDtoCondition
);
}
}
return
salesapdisMapper
.
selectList
(
wrapper
);
}
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
int
batchUpdate
(
List
<
SalesApDisplay
>
updateEntityList
)
{
if
(
CollectionUtils
.
isEmpty
(
updateEntityList
)){
return
0
;
}
int
count
=
0
;
for
(
SalesApDisplay
salesApDisplay
:
updateEntityList
)
{
count
+=
salesapdisMapper
.
updateById
(
salesApDisplay
);
}
return
count
;
}
private
LambdaQueryWrapper
<
SalesApDisplay
>
buildWq
(
SalesApWq
salesApWq
)
{
private
LambdaQueryWrapper
<
SalesApDisplay
>
buildWq
(
SalesApWq
salesApWq
)
{
LambdaQueryWrapper
<
SalesApDisplay
>
qw
=
new
LambdaQueryWrapper
<>();
LambdaQueryWrapper
<
SalesApDisplay
>
qw
=
new
LambdaQueryWrapper
<>();
if
(
StringUtils
.
isNotBlank
(
salesApWq
.
getDealerCode
()))
{
if
(
StringUtils
.
isNotBlank
(
salesApWq
.
getDealerCode
()))
{
...
...
src/main/java/com/sfa/operation/enums/ImportApType.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
enums
;
import
lombok.Getter
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 16:15
* @Description: 店内执行填报 导入 类型枚举
*/
@Getter
public
enum
ImportApType
{
/**
* 常规陈列导入策略
*/
NORMAL_DISPLAY_IMPORT
(
"normalDisplayImportStrategy"
),
/**
* 档期计划导入策略
*/
PROMOTION_PLAN_IMPORT
(
"promotionPlanImportStrategy"
),
/**
* 零食陈列导入策略
*/
SNACK_DISPLAY_IMPORT
(
"snackDisplayImportStrategy"
),
/**
* 三米两秒导入策略
*/
THREE_METER_TWO_SECONDS_IMPORT
(
"threeMeterSecondsImportStrategy"
),
/**
* 六小金刚导入策略
*/
SIX_KINGkONG_IMPORT
(
"sixKingKongImportStrategy"
);
private
final
String
importStrategy
;
ImportApType
(
String
importStrategy
)
{
this
.
importStrategy
=
importStrategy
;
}
}
src/main/java/com/sfa/operation/factory/ApExportExcelStrategyFactory.java
浏览文件 @
db2605b2
...
@@ -26,8 +26,6 @@ public class ApExportExcelStrategyFactory {
...
@@ -26,8 +26,6 @@ public class ApExportExcelStrategyFactory {
public
IExportApExcelStrategy
getStrategy
(
String
exportApType
)
{
public
IExportApExcelStrategy
getStrategy
(
String
exportApType
)
{
System
.
out
.
println
(
exportApExcelStrategyMap
.
size
());
System
.
out
.
println
(
exportApType
);
if
(
exportApType
==
null
||
exportApType
.
trim
().
isEmpty
())
{
if
(
exportApType
==
null
||
exportApType
.
trim
().
isEmpty
())
{
log
.
error
(
"AP导出Excel策略工厂:传入的导出类型为空!"
);
log
.
error
(
"AP导出Excel策略工厂:传入的导出类型为空!"
);
throw
new
IllegalArgumentException
(
"传入的导出类型为空!"
);
throw
new
IllegalArgumentException
(
"传入的导出类型为空!"
);
...
@@ -40,9 +38,8 @@ public class ApExportExcelStrategyFactory {
...
@@ -40,9 +38,8 @@ public class ApExportExcelStrategyFactory {
throw
new
IllegalArgumentException
(
"传入的导出类型不存在!"
);
throw
new
IllegalArgumentException
(
"传入的导出类型不存在!"
);
}
}
//
3.
从枚举中获取策略Bean名称,再从Map中查找策略
// 从枚举中获取策略Bean名称,再从Map中查找策略
String
strategyBeanName
=
typeEnum
.
getStrategy
();
String
strategyBeanName
=
typeEnum
.
getStrategy
();
System
.
out
.
println
(
strategyBeanName
);
IExportApExcelStrategy
strategy
=
exportApExcelStrategyMap
.
get
(
strategyBeanName
);
IExportApExcelStrategy
strategy
=
exportApExcelStrategyMap
.
get
(
strategyBeanName
);
if
(
strategy
==
null
)
{
if
(
strategy
==
null
)
{
log
.
error
(
"AP导出Excel策略工厂:未找到对应的导出策略!目标策略Bean名称为:{}"
,
strategyBeanName
);
log
.
error
(
"AP导出Excel策略工厂:未找到对应的导出策略!目标策略Bean名称为:{}"
,
strategyBeanName
);
...
...
src/main/java/com/sfa/operation/factory/ApImportExcelStrategyFactory.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
factory
;
import
com.sfa.operation.enums.ImportApType
;
import
com.sfa.operation.strategy.IImportApExcelStrategy
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Component
;
import
java.util.Map
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 16:23
* @Description: 店内执行上报 - 填报- 导入策略工厂类
*/
@Slf4j
@Component
public
class
ApImportExcelStrategyFactory
{
private
final
Map
<
String
,
IImportApExcelStrategy
>
importApExcelStrategyMap
;
//构造器注入
@Autowired
public
ApImportExcelStrategyFactory
(
Map
<
String
,
IImportApExcelStrategy
>
importApExcelStrategyMap
)
{
this
.
importApExcelStrategyMap
=
importApExcelStrategyMap
;
}
/**
* 获取策略
* @param importApType 点击导入的类型
* @return 对应策略实现类
*/
public
IImportApExcelStrategy
getStrategy
(
String
importApType
)
{
log
.
info
(
"店内执行填报导入策略注入开始,目标类型为:{}"
,
importApType
);
// 参数校验
if
(
importApType
==
null
||
importApType
.
trim
().
isEmpty
()){
log
.
error
(
"店内执行填报导入策略注入失败,失败原因:传入的导入类型为空!"
);
return
null
;
}
//转换为枚举类型
ImportApType
targetType
;
try
{
targetType
=
ImportApType
.
valueOf
(
importApType
);
}
catch
(
IllegalArgumentException
e
)
{
log
.
error
(
"店内执行填报导入策略注入失败,失败原因:传入的导入类型不存在!目标类型为:{}"
,
importApType
);
return
null
;
}
// 从枚举中获取策略Bean名称,再从Map中查找策略
String
beanName
=
targetType
.
getImportStrategy
();
IImportApExcelStrategy
strategy
=
importApExcelStrategyMap
.
get
(
beanName
);
if
(
strategy
==
null
)
{
log
.
error
(
"店内执行填报导入策略注入失败,失败原因:未找到对应的导入策略!目标策略Bean名称为:{}"
,
beanName
);
return
null
;
}
log
.
info
(
"店内执行填报导入策略注入成功,目标策略Bean名称为:{}"
,
beanName
);
return
strategy
;
}
}
src/main/java/com/sfa/operation/pojo/sales/excel/SalesApDisplayImportExcelDto.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
pojo
.
sales
.
excel
;
import
lombok.Data
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 16:05
* @Description: 店内执行上报 - 填报 - 常规陈列 导入excel数据传输对象
*/
@Data
public
class
SalesApDisplayImportExcelDto
{
/**
* 主键ID
* 类型:Long
*/
private
Long
sadId
;
/**
* 计划月份
* 注意:Excel中是"YYYY-MM"字符串,导入时需转换为Date类型
*/
private
String
salesMonth
;
/**
* 销售大区
* 类型:String
*/
private
String
regionName
;
/**
* 销售战区
* 类型:String
*/
private
String
districtName
;
/**
* 经销商代码
* 类型:String
*/
private
String
dealerCode
;
/**
* 经销商名称
* 类型:String
*/
private
String
dealerName
;
/**
* 门店编码
* 类型:String
*/
private
String
storeCode
;
/**
* 门店名称
* 类型:String
*/
private
String
storeName
;
/**
* 系统名称
* 类型:String
*/
private
String
lineName
;
/**
* 计划主货架-形式
* 类型:String
*/
private
String
plannedMainShelfType
;
/**
* 实际主货架-形式
* 类型:String
*/
private
String
actualMainShelfType
;
/**
* 计划主货架-数量
* 类型:Integer
*/
private
Integer
plannedMainShelfQty
;
/**
* 实际主货架-数量
* 类型:Integer
*/
private
Integer
actualMainShelfQty
;
/**
* 计划端架-数量
* 类型:Double
*/
private
Double
plannedEndCapQty
;
/**
* 实际端架-数量
* 类型:Double
*/
private
Double
actualEndCapQty
;
/**
* 计划地堆-平米数(㎡)
* 类型:Double
*/
private
Double
plannedFloorStackArea
;
/**
* 实际地堆-平米数(㎡)
* 类型:Double
*/
private
Double
actualFloorStackArea
;
/**
* 计划地堆-数量
* 类型:Integer
*/
private
Integer
plannedFloorStackQty
;
/**
* 实际地堆-数量
* 类型:Integer
*/
private
Integer
actualFloorStackQty
;
/**
* 计划多点陈列-数量+形式
* 类型:String
*/
private
String
plannedMultiDisplay
;
/**
* 实际多点陈列-数量+形式
* 类型:String
*/
private
String
actualMultiDisplay
;
/**
* 计划挂条-数量+形式
* 类型:String
*/
private
String
plannedHangingStripQuantityForm
;
/**
* 实际挂条-数量+形式
* 类型:String
*/
private
String
actualHangingStripQuantityForm
;
/**
* 错误信息
* 类型:String
*/
private
String
errorMsg
;
/**
* 行号
* 类型:Integer
*/
private
Integer
rowNum
;
}
src/main/java/com/sfa/operation/pojo/sales/request/ImportApExcelRequest.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
pojo
.
sales
.
request
;
import
lombok.Data
;
/**
* @Author: DouXinYu
* @Date: 2025-12-11 16:12
* @Description: 店内执行上报 - 导入Excel请求参数
*/
@Data
public
class
ImportApExcelRequest
{
private
String
importApType
;
private
String
importApFilePath
;
private
String
uuid
;
}
src/main/java/com/sfa/operation/pojo/sales/vo/SalesApDisplayVo.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
pojo
.
sales
.
vo
;
import
lombok.Data
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 15:31
* @Description: 店内执行-填报要更新到表的字段
*/
@Data
public
class
SalesApDisplayVo
{
/**
* 主键ID
*/
private
Long
sadId
;
/**
* 主货架形式(实际)
*/
private
String
actualMainShelfType
;
/**
* 主货架数量(实际)
*/
private
Integer
actualMainShelfQty
;
/**
* 实际-主货架是否执行
* 执行主货架形式 >= 计划主货架形式 && 执行主货架数量 >= 计划主货架数量"
* 执行/未执行
*/
private
String
actualMainShelfExecuted
;
/**
* 端架数量(实际)
*/
private
Integer
actualEndCapQty
;
/**
* 实际-架是否执行
* 执行端架数量 >= 计划端架数量
*/
private
String
actualEndCapExecuted
;
/**
* 地堆平米数(实际)
*/
private
Double
actualFloorStackArea
;
/**
* 地堆数量(实际)
*/
private
Integer
actualFloorStackQty
;
/**
* 实际-地堆是否执行
* 执行平米数 >= 计划平米数 && 执行数量 >= 计划数量"
*/
private
String
actualFloorStackExecuted
;
/**
* 多点陈列数量+形式(实际)
*/
private
String
actualMultiDisplay
;
/**
* 实际-多点陈列是否执行
*
* actualMultiDisplay的值如下时:
* 执行与计划一致:执行
* 执行与计划不一致:未执行
*/
private
String
actualMultiDisplayExecuted
;
/**
* 挂条数量+形式(实际)
*/
private
String
actualHangingStripQuantityForm
;
/**
* 实际-挂条是否执行
*
* actualHangingStripQuantityForm的值如下时:
* 执行与计划一致:执行
* 执行与计划不一致:未执行
*/
private
String
hangingStripExecuted
;
}
src/main/java/com/sfa/operation/service/sales/IApDisplayCoreService.java
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
service
.
sales
;
package
com
.
sfa
.
operation
.
service
.
sales
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
java.util.List
;
/**
/**
* @author : liqiulin
* @author : liqiulin
* @date : 2025-09-16 16
* @date : 2025-09-16 16
...
@@ -19,4 +22,6 @@ public interface IApDisplayCoreService {
...
@@ -19,4 +22,6 @@ public interface IApDisplayCoreService {
void
putDisplayJDetail
(
SalesApRequest
request
);
void
putDisplayJDetail
(
SalesApRequest
request
);
void
putPromotionDetail
(
SalesApRequest
request
);
void
putPromotionDetail
(
SalesApRequest
request
);
int
batchUpdate
(
List
<
SalesApDisplay
>
updateEntityList
);
}
}
src/main/java/com/sfa/operation/service/sales/IApDisplayQueryService.java
浏览文件 @
db2605b2
...
@@ -2,7 +2,7 @@ package com.sfa.operation.service.sales;
...
@@ -2,7 +2,7 @@ package com.sfa.operation.service.sales;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.
domain.sales.wq.SalesApWq
;
import
com.sfa.operation.
pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
java.util.List
;
import
java.util.List
;
...
@@ -29,4 +29,6 @@ public interface IApDisplayQueryService {
...
@@ -29,4 +29,6 @@ public interface IApDisplayQueryService {
Object
queryDeptAPReport
(
SalesApRequest
request
);
Object
queryDeptAPReport
(
SalesApRequest
request
);
List
<
SalesApDisplay
>
queryDataListByCondition
(
SalesApRequest
build
);
List
<
SalesApDisplay
>
queryDataListByCondition
(
SalesApRequest
build
);
List
<
SalesApDisplay
>
queryByCondition
(
List
<
SalesApDisplayImportExcelDto
>
queryParam
);
}
}
src/main/java/com/sfa/operation/service/sales/export/IImportExcelService.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
service
.
sales
.
export
;
import
com.sfa.common.core.domain.R
;
import
com.sfa.operation.pojo.sales.request.ImportApExcelRequest
;
/**
* @Author: DouXinYu
* @Date: 2025-12-12 13:11
* @Description: 导入excel服务接口
*/
public
interface
IImportExcelService
{
R
importApExcel
(
ImportApExcelRequest
request
);
R
updateApEntity
(
ImportApExcelRequest
request
);
}
src/main/java/com/sfa/operation/service/sales/export/impl/ImportExcelServiceImpl.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
service
.
sales
.
export
.
impl
;
import
com.alibaba.fastjson2.JSONObject
;
import
com.sfa.common.core.domain.R
;
import
com.sfa.operation.factory.ApImportExcelStrategyFactory
;
import
com.sfa.operation.pojo.sales.request.ImportApExcelRequest
;
import
com.sfa.operation.service.sales.export.IImportExcelService
;
import
com.sfa.operation.strategy.IImportApExcelStrategy
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
org.springframework.stereotype.Service
;
import
java.util.Collection
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
/**
* @Author: DouXinYu
* @Date: 2025-12-12 13:11
* @Description: 导入excel服务实现类
*/
@Slf4j
@Service
public
class
ImportExcelServiceImpl
implements
IImportExcelService
{
@Autowired
private
ApImportExcelStrategyFactory
apImportExcelStrategyFactory
;
@Autowired
private
StringRedisTemplate
stringRedisTemplate
;
public
static
final
String
REDIS_KEY_PREFIX
=
"import_excel_ap:"
;
/**
* 店内执行上上报 -导入方法
*
* @param request 导入excel请求参数
* @return 导入结果
*/
@Override
public
R
importApExcel
(
ImportApExcelRequest
request
)
{
// 参数校验
if
(
request
==
null
||
request
.
getImportApFilePath
()
==
null
||
request
.
getImportApFilePath
().
trim
().
isEmpty
()){
return
R
.
fail
(
"导入文件路径不能为空!"
);
}
if
(
request
.
getImportApType
()
==
null
||
request
.
getImportApType
().
trim
().
isEmpty
()){
return
R
.
fail
(
"导入类型不能为空!"
);
}
// 获取策略
IImportApExcelStrategy
strategy
=
apImportExcelStrategyFactory
.
getStrategy
(
request
.
getImportApType
());
if
(
strategy
==
null
){
return
R
.
fail
(
"未找到对应的导入策略!"
);
}
// 执行导入的数据验证(根据不同的策略独自设计验证)
Map
<
String
,
Object
>
result
=
strategy
.
execute
(
request
.
getImportApFilePath
());
Integer
failCount
=
(
Integer
)
result
.
get
(
"failCount"
);
//failCount>0 时 返回错误信息
if
(
failCount
>
0
)
{
log
.
error
(
"导入失败,失败条数:{}"
,
failCount
);
return
R
.
fail
(
result
);
}
else
{
String
uuid
=
(
String
)
result
.
getOrDefault
(
"uuid"
,
""
);
String
redisKey
=
REDIS_KEY_PREFIX
+
uuid
;
//将数据保存躁redis中
stringRedisTemplate
.
opsForValue
().
set
(
redisKey
,
JSONObject
.
toJSONString
(
result
),
30
,
TimeUnit
.
MINUTES
);
log
.
info
(
"数据保存至redis中,redisKey={},result={}"
,
redisKey
,
JSONObject
.
toJSONString
(
result
));
}
return
R
.
ok
(
result
);
}
/**
* 前端点击确认后 更新数据
* @param request 导入excel请求参数
* @return 导入结果
*/
@Override
public
R
updateApEntity
(
ImportApExcelRequest
request
)
{
if
(
request
.
getImportApType
()
==
null
||
request
.
getImportApType
().
trim
().
isEmpty
()){
return
R
.
fail
(
"导入类型不能为空!"
);
}
if
(
request
.
getUuid
()
==
null
||
request
.
getUuid
().
trim
().
isEmpty
()){
return
R
.
fail
(
"导入数据标识不能为空!"
);
}
// 获取策略
IImportApExcelStrategy
strategy
=
apImportExcelStrategyFactory
.
getStrategy
(
request
.
getImportApType
());
if
(
strategy
==
null
){
return
R
.
fail
(
"未找到对应的导入策略!"
);
}
//从redis获取数据
String
redisKey
=
REDIS_KEY_PREFIX
+
request
.
getUuid
();
String
redisValue
=
stringRedisTemplate
.
opsForValue
().
get
(
redisKey
);
//解析jsonToDtoList
List
list
=
strategy
.
getTransactionJsonToObject
(
redisValue
);
//批量更新
String
result
=
strategy
.
updateDisplay
(
list
);
if
(
"更新失败"
.
equals
(
result
))
{
return
R
.
fail
(
result
);
}
return
R
.
ok
(
result
);
}
}
src/main/java/com/sfa/operation/service/sales/impl/ApDisplayCoreServiceImpl.java
浏览文件 @
db2605b2
...
@@ -3,6 +3,7 @@ package com.sfa.operation.service.sales.impl;
...
@@ -3,6 +3,7 @@ package com.sfa.operation.service.sales.impl;
import
cn.hutool.core.date.DatePattern
;
import
cn.hutool.core.date.DatePattern
;
import
com.sfa.common.security.utils.SecurityUtils
;
import
com.sfa.common.security.utils.SecurityUtils
;
import
com.sfa.operation.domain.sales.dao.*
;
import
com.sfa.operation.domain.sales.dao.*
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.response.*
;
import
com.sfa.operation.pojo.sales.response.*
;
import
com.sfa.operation.service.sales.IApDisplayCoreService
;
import
com.sfa.operation.service.sales.IApDisplayCoreService
;
...
@@ -12,6 +13,7 @@ import org.springframework.stereotype.Service;
...
@@ -12,6 +13,7 @@ import org.springframework.stereotype.Service;
import
java.text.ParseException
;
import
java.text.ParseException
;
import
java.util.Date
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Objects
;
/**
/**
...
@@ -78,6 +80,16 @@ public class ApDisplayCoreServiceImpl implements IApDisplayCoreService {
...
@@ -78,6 +80,16 @@ public class ApDisplayCoreServiceImpl implements IApDisplayCoreService {
salesApDisplayJDao
.
updateDetail
(
djDto
);
salesApDisplayJDao
.
updateDetail
(
djDto
);
}
}
/**
*
* @param updateEntityList
* @return
*/
@Override
public
int
batchUpdate
(
List
<
SalesApDisplay
>
updateEntityList
)
{
return
salesApDisplayDao
.
batchUpdate
(
updateEntityList
);
}
@Override
@Override
public
void
putPromotionDetail
(
SalesApRequest
request
)
{
public
void
putPromotionDetail
(
SalesApRequest
request
)
{
// 修改DB 日期值为null
// 修改DB 日期值为null
...
@@ -110,4 +122,6 @@ public class ApDisplayCoreServiceImpl implements IApDisplayCoreService {
...
@@ -110,4 +122,6 @@ public class ApDisplayCoreServiceImpl implements IApDisplayCoreService {
e
.
printStackTrace
();
e
.
printStackTrace
();
}
}
}
}
}
}
src/main/java/com/sfa/operation/service/sales/impl/ApDisplayQueryServiceImpl.java
浏览文件 @
db2605b2
...
@@ -2,23 +2,17 @@ package com.sfa.operation.service.sales.impl;
...
@@ -2,23 +2,17 @@ package com.sfa.operation.service.sales.impl;
import
cn.hutool.core.date.DatePattern
;
import
cn.hutool.core.date.DatePattern
;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.date.DateUtil
;
import
com.sfa.common.core.utils.StringUtils
;
import
com.sfa.common.core.utils.bean.BeanUtils
;
import
com.sfa.common.core.utils.bean.BeanUtils
;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.common.core.web.domain.PageInfo
;
import
com.sfa.operation.domain.feishu.dao.IQinceMarketEmployeeDao
;
import
com.sfa.operation.domain.sales.dao.*
;
import
com.sfa.operation.domain.sales.dao.*
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.domain.sales.wq.SalesApWq
;
import
com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.service.sales.IApDisplayQueryService
;
import
com.sfa.operation.service.sales.IApDisplayQueryService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
import
java.awt.dnd.Autoscroll
;
import
java.util.Collections
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.List
;
/**
/**
...
@@ -86,6 +80,11 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService {
...
@@ -86,6 +80,11 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService {
return
salesApDisplayDao
.
queryDataListByCondition
(
build
(
request
));
return
salesApDisplayDao
.
queryDataListByCondition
(
build
(
request
));
}
}
@Override
public
List
<
SalesApDisplay
>
queryByCondition
(
List
<
SalesApDisplayImportExcelDto
>
validDtoList
)
{
return
salesApDisplayDao
.
queryByCondition
(
validDtoList
);
}
private
SalesApWq
build
(
SalesApRequest
salesApRequest
){
private
SalesApWq
build
(
SalesApRequest
salesApRequest
){
SalesApWq
salesApWq
=
new
SalesApWq
();
SalesApWq
salesApWq
=
new
SalesApWq
();
BeanUtils
.
copyProperties
(
salesApRequest
,
salesApWq
);
BeanUtils
.
copyProperties
(
salesApRequest
,
salesApWq
);
...
...
src/main/java/com/sfa/operation/strategy/IImportApExcelStrategy.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
strategy
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
java.io.InputStream
;
import
java.util.List
;
import
java.util.Map
;
/**
* @Author: DouXinYu
* @Date: 2025-12-10 16:21
* @Description: 店内执行填报 - 导入策略接口
*/
public
interface
IImportApExcelStrategy
<
T
>
{
/**
* 获取导入列配置(复用导出列配置)
*/
List
<
ExportColumnConfig
>
getImportColumnConfig
();
/**
* 解析Excel文件流为DTO列表
*/
List
<
T
>
parseExcelToDtoList
(
InputStream
inputStream
,
String
flePathUrl
,
Map
<
String
,
List
<
T
>>
errorMap
);
/**
* 预处理DTO数据(补充关联数据)
*/
void
preprocessData
(
List
<
T
>
dtoList
);
/**
* 数据库批量操作(新增/更新)
*/
int
queryAndBatchOperate
(
List
<
T
>
dtoList
);
/**
* 构建更新实体列表
*/
List
<?>
buildUpdateEntityList
(
List
<
T
>
dtoList
);
/**
* 查询数据库数据
*/
List
<?>
queryData
(
List
<
T
>
dto
);
/**
* 更新数据库数据
*/
String
updateDisplay
(
List
<
T
>
dtoList
);
List
<
T
>
getTransactionJsonToObject
(
String
json
);
/**
* 导入核心执行方法
*/
Map
<
String
,
Object
>
execute
(
String
flePathUrl
);
/**
* 获取导入Sheet名称(默认方法)
*/
default
String
getImportSheetName
()
{
return
""
;
}
}
src/main/java/com/sfa/operation/strategy/impl/NormalDisplayExportStrategyImpl.java
→
src/main/java/com/sfa/operation/strategy/impl/
exports/
NormalDisplayExportStrategyImpl.java
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
strategy
.
impl
;
package
com
.
sfa
.
operation
.
strategy
.
impl
.
exports
;
import
com.sfa.common.core.constant.RoleConstants
;
import
com.sfa.common.core.enums.ECode
;
import
com.sfa.common.core.exception.CheckedException
;
import
com.sfa.common.security.utils.SecurityUtils
;
import
com.sfa.operation.config.ConstantValue
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.pojo.sales.request.SalesApRequest
;
import
com.sfa.operation.service.qc.IQinceMarketEmployeeService
;
import
com.sfa.operation.service.sales.IApDisplayQueryService
;
import
com.sfa.operation.service.sales.IApDisplayQueryService
;
import
com.sfa.operation.strategy.IExportApExcelStrategy
;
import
com.sfa.operation.strategy.IExportApExcelStrategy
;
import
com.sfa.operation.util.excel.ExcelStyleUtils
;
import
com.sfa.operation.util.excel.ExcelStyleUtils
;
import
com.sfa.system.api.domain.SysRole
;
import
com.sfa.system.api.model.LoginUser
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Component
;
import
org.springframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Optional
;
/**
/**
* @Author: DouXinYu
* @Author: DouXinYu
...
@@ -34,10 +25,6 @@ public class NormalDisplayExportStrategyImpl implements IExportApExcelStrategy {
...
@@ -34,10 +25,6 @@ public class NormalDisplayExportStrategyImpl implements IExportApExcelStrategy {
@Autowired
@Autowired
private
IApDisplayQueryService
apDisplayQueryService
;
private
IApDisplayQueryService
apDisplayQueryService
;
@Autowired
private
IQinceMarketEmployeeService
qinceMarketEmployeeService
;
@Autowired
private
ConstantValue
constantValue
;
/**
/**
* 获取导出列配置
* 获取导出列配置
...
...
src/main/java/com/sfa/operation/strategy/impl/imports/NormalDisplayImportStrategyImpl.java
0 → 100644
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
strategy
.
impl
.
imports
;
import
com.alibaba.nacos.shaded.com.google.gson.reflect.TypeToken
;
import
com.google.gson.Gson
;
import
com.google.gson.JsonObject
;
import
com.google.gson.JsonParser
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.domain.sales.entity.SalesApDisplay
;
import
com.sfa.operation.pojo.sales.excel.SalesApDisplayImportExcelDto
;
import
com.sfa.operation.pojo.sales.vo.SalesApDisplayVo
;
import
com.sfa.operation.service.sales.impl.ApDisplayCoreServiceImpl
;
import
com.sfa.operation.service.sales.impl.ApDisplayQueryServiceImpl
;
import
com.sfa.operation.strategy.IImportApExcelStrategy
;
import
com.sfa.operation.strategy.impl.exports.NormalDisplayExportStrategyImpl
;
import
com.sfa.operation.util.excel.ExcelUtils
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Component
;
import
org.springframework.transaction.annotation.Transactional
;
import
java.io.InputStream
;
import
java.util.*
;
import
java.util.stream.Collectors
;
/**
* @Author: DouXinYu
* @Date: 2025-12-11 17:31
* @Description: AP导入excel(填报)控制类
*/
@Slf4j
@Component
(
"normalDisplayImportStrategy"
)
public
class
NormalDisplayImportStrategyImpl
implements
IImportApExcelStrategy
<
SalesApDisplayImportExcelDto
>
{
@Autowired
private
NormalDisplayExportStrategyImpl
normalDisplayExportStrategy
;
@Autowired
private
ApDisplayQueryServiceImpl
salesApQueryDisplayService
;
@Autowired
private
ApDisplayCoreServiceImpl
salesApDisplayCoreService
;
@Override
public
List
<
ExportColumnConfig
>
getImportColumnConfig
()
{
return
normalDisplayExportStrategy
.
getExportColumnConfig
();
}
@Override
public
List
<
SalesApDisplayImportExcelDto
>
parseExcelToDtoList
(
InputStream
inputStream
,
String
filePathUrl
,
Map
<
String
,
List
<
SalesApDisplayImportExcelDto
>>
errorMap
)
{
// 基础校验
if
(
inputStream
==
null
||
StringUtils
.
isBlank
(
filePathUrl
))
{
SalesApDisplayImportExcelDto
errorDto
=
buildSimpleErrorDto
(
"文件流/URL为空"
);
errorMap
.
put
(
"参数异常"
,
Collections
.
singletonList
(
errorDto
));
// 修改:返回含错误DTO的列表
return
Collections
.
singletonList
(
errorDto
);
}
// 复用ExcelUtils解析(内置列校验/错误收集)
String
fileName
=
ExcelUtils
.
parseFileNameFromOssUrl
(
filePathUrl
);
// String fileName = ExcelUtils.extractFileNameFromUrl(filePathUrl);
List
<
ExportColumnConfig
>
columnConfigs
=
getImportColumnConfig
();
// 基础校验
return
ExcelUtils
.
readApExcelWithColumnConfig
(
inputStream
,
fileName
,
columnConfigs
,
errorMap
,
SalesApDisplayImportExcelDto
.
class
);
}
@Override
public
void
preprocessData
(
List
<
SalesApDisplayImportExcelDto
>
dtoList
)
{
if
(
CollectionUtils
.
isEmpty
(
dtoList
))
{
return
;
}
dtoList
.
forEach
(
dto
->
{
// 修改:初始化errorMsg为空字符串,避免null
if
(
dto
.
getErrorMsg
()
==
null
)
{
dto
.
setErrorMsg
(
""
);
}
StringBuilder
errorMsg
=
new
StringBuilder
();
if
(
dto
.
getSadId
()
==
null
)
{
errorMsg
.
append
(
"序号不能为空;"
);
}
// 合并错误信息
if
(
errorMsg
.
length
()
>
0
)
{
String
newError
=
errorMsg
.
toString
().
replaceAll
(
";$"
,
""
);
dto
.
setErrorMsg
(
StringUtils
.
isBlank
(
dto
.
getErrorMsg
())
?
newError
:
dto
.
getErrorMsg
()
+
";"
+
newError
);
}
});
}
@Override
public
int
queryAndBatchOperate
(
List
<
SalesApDisplayImportExcelDto
>
dtoList
)
{
if
(
CollectionUtils
.
isEmpty
(
dtoList
))
{
return
0
;
}
// 过滤有效数据(无预处理错误)
List
<
SalesApDisplayImportExcelDto
>
validDtoList
=
dtoList
.
stream
()
.
filter
(
d
->
StringUtils
.
isBlank
(
d
.
getErrorMsg
()))
.
collect
(
Collectors
.
toList
());
if
(
CollectionUtils
.
isEmpty
(
validDtoList
))
{
return
0
;
}
// 先进行查询验证,确保所有数据都能在数据库中找到对应记录
int
successCount
=
0
;
List
<
SalesApDisplay
>
salesApDisplayList
=
queryData
(
validDtoList
);
Map
<
String
,
SalesApDisplay
>
displayMap
=
new
HashMap
<>();
for
(
SalesApDisplay
display
:
salesApDisplayList
)
{
String
matchKey
=
buildMatchKey
(
display
.
getSadId
(),
display
.
getRegionName
(),
display
.
getDealerName
(),
display
.
getLineName
()
);
displayMap
.
put
(
matchKey
,
display
);
}
for
(
SalesApDisplayImportExcelDto
dto
:
validDtoList
)
{
String
dtoMatchKey
=
buildMatchKey
(
dto
.
getSadId
(),
dto
.
getRegionName
(),
dto
.
getDealerName
(),
dto
.
getLineName
()
);
if
(
displayMap
.
containsKey
(
dtoMatchKey
))
{
successCount
++;
}
else
{
// 错误提示仅显示「序号为{sadId}的数据不存在」
String
errorMsg
=
dto
.
getSadId
()
!=
null
?
"序号为"
+
dto
.
getSadId
()
+
"的数据不存在"
:
"序号为空的数据不存在"
;
// sadId为空时的兜底提示
// 保留原有错误信息拼接逻辑(如有其他错误,追加当前提示)
dto
.
setErrorMsg
(
StringUtils
.
isNotBlank
(
dto
.
getErrorMsg
())
?
dto
.
getErrorMsg
()
+
";"
+
errorMsg
:
errorMsg
);
}
}
return
successCount
;
}
@Override
public
List
<
SalesApDisplay
>
buildUpdateEntityList
(
List
<
SalesApDisplayImportExcelDto
>
dtoList
)
{
// 空值快速返回
if
(
CollectionUtils
.
isEmpty
(
dtoList
))
{
return
Collections
.
emptyList
();
}
List
<
SalesApDisplay
>
updateEntityList
=
new
ArrayList
<>();
// 批量查询所有DTO对应的计划数据,避免循环内重复查询
List
<
SalesApDisplay
>
salesApDisplayList
=
queryData
(
dtoList
);
Map
<
String
,
SalesApDisplay
>
displayMap
=
new
HashMap
<>();
for
(
SalesApDisplay
display
:
salesApDisplayList
)
{
String
matchKey
=
buildMatchKey
(
display
.
getSadId
(),
display
.
getRegionName
(),
display
.
getDealerName
(),
display
.
getLineName
()
);
displayMap
.
put
(
matchKey
,
display
);
}
for
(
SalesApDisplayImportExcelDto
dto
:
dtoList
)
{
SalesApDisplayVo
salesApDisplayVo
=
new
SalesApDisplayVo
();
BeanUtils
.
copyProperties
(
dto
,
salesApDisplayVo
);
// 从Map中获取当前DTO对应的计划数据
SalesApDisplay
salesApDisplay
=
displayMap
.
get
(
buildMatchKey
(
dto
.
getSadId
(),
dto
.
getRegionName
(),
dto
.
getDealerName
(),
dto
.
getLineName
()
));
if
(
salesApDisplay
==
null
)
{
log
.
warn
(
"序号为{}的DTO无匹配计划数据,跳过处理"
,
dto
.
getSadId
());
continue
;
}
// 主货架执行状态计算
if
(
salesApDisplay
.
getPlannedMainShelfType
()
!=
null
&&
salesApDisplay
.
getPlannedMainShelfQty
()
!=
null
&&
salesApDisplayVo
.
getActualMainShelfType
()
!=
null
&&
salesApDisplayVo
.
getActualMainShelfQty
()
!=
null
)
{
boolean
mainShelfTypeMatch
=
salesApDisplayVo
.
getActualMainShelfType
().
equals
(
salesApDisplay
.
getPlannedMainShelfType
());
boolean
mainShelfQtySufficient
=
salesApDisplayVo
.
getActualMainShelfQty
()
>=
salesApDisplay
.
getPlannedMainShelfQty
();
salesApDisplayVo
.
setActualMainShelfExecuted
((
mainShelfTypeMatch
&&
mainShelfQtySufficient
)
?
"执行"
:
"未执行"
);
}
// 端架执行状态计算
if
(
salesApDisplayVo
.
getActualEndCapQty
()
!=
null
&&
salesApDisplay
.
getPlannedEndCapQty
()
!=
null
)
{
salesApDisplayVo
.
setActualEndCapExecuted
(
salesApDisplayVo
.
getActualEndCapQty
()
>=
salesApDisplay
.
getPlannedEndCapQty
()
?
"执行"
:
"未执行"
);
}
// 地堆执行状态计算
if
(
salesApDisplay
.
getPlannedFloorStackArea
()
!=
null
&&
salesApDisplay
.
getPlannedFloorStackQty
()
!=
null
&&
salesApDisplayVo
.
getActualFloorStackArea
()
!=
null
&&
salesApDisplayVo
.
getActualFloorStackQty
()
!=
null
)
{
boolean
areaSufficient
=
salesApDisplayVo
.
getActualFloorStackArea
()
>=
salesApDisplay
.
getPlannedFloorStackArea
();
boolean
qtySufficient
=
salesApDisplayVo
.
getActualFloorStackQty
()
>=
salesApDisplay
.
getPlannedFloorStackQty
();
salesApDisplayVo
.
setActualFloorStackExecuted
((
areaSufficient
&&
qtySufficient
)
?
"执行"
:
"未执行"
);
}
// 多点陈列执行状态
if
(
salesApDisplay
.
getPlannedMultiDisplay
()
!=
null
&&
salesApDisplayVo
.
getActualMultiDisplay
()
!=
null
)
{
salesApDisplayVo
.
setActualMultiDisplayExecuted
(
StringUtils
.
equals
(
"执行与计划一致"
,
salesApDisplayVo
.
getActualMultiDisplay
())
?
"执行"
:
"未执行"
);
}
// 挂条执行状态字段赋值错误
if
(
salesApDisplay
.
getPlannedHangingStripQuantityForm
()
!=
null
&&
salesApDisplayVo
.
getActualHangingStripQuantityForm
()
!=
null
)
{
salesApDisplayVo
.
setHangingStripExecuted
(
StringUtils
.
equals
(
"执行与计划一致"
,
salesApDisplayVo
.
getActualHangingStripQuantityForm
())
?
"执行"
:
"未执行"
);
}
// VO转实体逻辑
SalesApDisplay
updatedEntity
=
new
SalesApDisplay
();
BeanUtils
.
copyProperties
(
salesApDisplayVo
,
updatedEntity
);
updateEntityList
.
add
(
updatedEntity
);
}
return
updateEntityList
;
}
@Override
public
Map
<
String
,
Object
>
execute
(
String
filePathUrl
)
{
Map
<
String
,
Object
>
resultMap
=
new
HashMap
<>(
5
);
InputStream
inputStream
=
null
;
try
{
// 获取文件流(修改:文件流为空时构建错误DTO)
inputStream
=
ExcelUtils
.
getOssFileInputStream
(
filePathUrl
);
if
(
inputStream
==
null
)
{
SalesApDisplayImportExcelDto
errorDto
=
buildSimpleErrorDto
(
"文件流获取失败:OSS文件不存在或权限不足"
);
List
<
SalesApDisplayImportExcelDto
>
errorList
=
Collections
.
singletonList
(
errorDto
);
resultMap
.
put
(
"uuid"
,
UUID
.
randomUUID
().
toString
());
resultMap
.
put
(
"table"
,
errorList
);
resultMap
.
put
(
"successCount"
,
0
);
resultMap
.
put
(
"failCount"
,
errorList
.
size
());
resultMap
.
put
(
"errorMsg"
,
"文件流获取失败:OSS文件不存在或权限不足"
);
return
resultMap
;
}
// 解析Excel
Map
<
String
,
List
<
SalesApDisplayImportExcelDto
>>
tempErrorMap
=
new
HashMap
<>();
List
<
SalesApDisplayImportExcelDto
>
dtoList
=
parseExcelToDtoList
(
inputStream
,
filePathUrl
,
tempErrorMap
);
// 数据预处理
preprocessData
(
dtoList
);
//批量验证/操作)
int
successCount
=
queryAndBatchOperate
(
dtoList
);
// 5封装返回结果
resultMap
.
put
(
"uuid"
,
UUID
.
randomUUID
().
toString
());
resultMap
.
put
(
"table"
,
dtoList
);
resultMap
.
put
(
"successCount"
,
successCount
);
resultMap
.
put
(
"failCount"
,
dtoList
.
size
()
-
successCount
);
resultMap
.
put
(
"errorMsg"
,
""
);
}
catch
(
Exception
e
)
{
log
.
error
(
"常规陈列导入失败,filePathUrl={}"
,
filePathUrl
,
e
);
// 修改:异常时构建错误DTO,存入table
SalesApDisplayImportExcelDto
errorDto
=
buildSimpleErrorDto
(
"导入失败:"
+
e
.
getMessage
());
List
<
SalesApDisplayImportExcelDto
>
errorList
=
Collections
.
singletonList
(
errorDto
);
resultMap
.
put
(
"uuid"
,
UUID
.
randomUUID
().
toString
());
resultMap
.
put
(
"table"
,
errorList
);
resultMap
.
put
(
"successCount"
,
0
);
resultMap
.
put
(
"failCount"
,
errorList
.
size
());
resultMap
.
put
(
"errorMsg"
,
errorList
.
get
(
0
).
getErrorMsg
());
}
finally
{
// 关闭流(不变)
if
(
inputStream
!=
null
)
{
try
{
inputStream
.
close
();
}
catch
(
Exception
e
)
{
log
.
error
(
"关闭文件流失败"
,
e
);
}
}
}
return
resultMap
;
}
@Override
public
List
<
SalesApDisplay
>
queryData
(
List
<
SalesApDisplayImportExcelDto
>
dto
)
{
// 查询数据库 此处查询正常只会返回一个对象
return
salesApQueryDisplayService
.
queryByCondition
(
dto
);
}
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
String
updateDisplay
(
List
<
SalesApDisplayImportExcelDto
>
dtoList
)
{
if
(
dtoList
==
null
||
dtoList
.
isEmpty
())
{
return
"更新失败"
;
}
List
<
SalesApDisplay
>
salesApDisplayList
=
buildUpdateEntityList
(
dtoList
);
if
(
salesApDisplayList
.
isEmpty
())
{
return
"更新失败"
;
}
int
i
=
salesApDisplayCoreService
.
batchUpdate
(
salesApDisplayList
);
return
i
>
0
?
"更新成功"
:
"更新失败"
;
}
@Override
public
List
<
SalesApDisplayImportExcelDto
>
getTransactionJsonToObject
(
String
json
)
{
if
(
StringUtils
.
isBlank
(
json
))
{
return
Collections
.
emptyList
();
}
try
{
JsonObject
jsonObject
=
JsonParser
.
parseString
(
json
).
getAsJsonObject
();
// 获取table字段
if
(
jsonObject
.
has
(
"table"
)
&&
!
jsonObject
.
get
(
"table"
).
isJsonNull
())
{
Gson
gson
=
new
Gson
();
String
tableJson
=
jsonObject
.
get
(
"table"
).
toString
();
return
gson
.
fromJson
(
tableJson
,
new
TypeToken
<
List
<
SalesApDisplayImportExcelDto
>>(){}.
getType
());
}
return
Collections
.
emptyList
();
}
catch
(
Exception
e
)
{
log
.
error
(
"解析JSON失败: {}"
,
json
,
e
);
return
Collections
.
emptyList
();
}
}
/**
* 统一构建组合匹配键(保证DTO和查询结果的键规则一致)
*/
private
String
buildMatchKey
(
Long
sadId
,
String
regionName
,
String
dealerName
,
String
lineName
)
{
return
(
sadId
==
null
?
""
:
sadId
)
+
"_"
+
(
regionName
==
null
?
""
:
regionName
.
trim
())
+
"_"
+
(
dealerName
==
null
?
""
:
dealerName
.
trim
())
+
"_"
+
(
lineName
==
null
?
""
:
lineName
.
trim
());
}
/**
* 构建错误DTO(仅用于参数异常场景)
*/
public
SalesApDisplayImportExcelDto
buildSimpleErrorDto
(
String
errorMsg
)
{
SalesApDisplayImportExcelDto
dto
=
new
SalesApDisplayImportExcelDto
();
dto
.
setErrorMsg
(
errorMsg
);
return
dto
;
}
}
\ No newline at end of file
src/main/java/com/sfa/operation/util/excel/ExcelUtils.java
浏览文件 @
db2605b2
package
com
.
sfa
.
operation
.
util
.
excel
;
package
com
.
sfa
.
operation
.
util
.
excel
;
import
cn.hutool.core.bean.BeanUtil
;
import
cn.hutool.core.bean.BeanUtil
;
import
com.aliyun.oss.OSS
;
import
com.aliyun.oss.OSSClientBuilder
;
import
com.aliyun.oss.model.GetObjectRequest
;
import
com.aliyuncs.DefaultAcsClient
;
import
com.aliyuncs.auth.sts.AssumeRoleRequest
;
import
com.aliyuncs.auth.sts.AssumeRoleResponse
;
import
com.aliyuncs.exceptions.ClientException
;
import
com.aliyuncs.http.MethodType
;
import
com.aliyuncs.profile.DefaultProfile
;
import
com.aliyuncs.profile.IClientProfile
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.config.ExportColumnConfig
;
import
com.sfa.operation.config.OssConfigProperties
;
import
lombok.extern.slf4j.Slf4j
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.poi.hssf.usermodel.HSSFWorkbook
;
import
org.apache.poi.ss.usermodel.*
;
import
org.apache.poi.ss.usermodel.*
;
import
org.apache.poi.ss.util.CellRangeAddressList
;
import
org.apache.poi.ss.util.CellRangeAddressList
;
import
org.apache.poi.xssf.streaming.SXSSFWorkbook
;
import
org.apache.poi.xssf.streaming.SXSSFWorkbook
;
import
org.apache.poi.xssf.usermodel.XSSFWorkbook
;
import
org.apache.poi.xssf.usermodel.XSSFWorkbook
;
import
org.s
lf4j.Logger
;
import
org.s
pringframework.beans.factory.annotation.Autowired
;
import
org.s
lf4j.LoggerFactory
;
import
org.s
pringframework.stereotype.Component
;
import
javax.annotation.PostConstruct
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.
ByteArrayOutputStream
;
import
java.io.
*
;
import
java.
io.IOException
;
import
java.
math.BigDecimal
;
import
java.
io.OutputStream
;
import
java.
net.URLDecoder
;
import
java.net.URLEncoder
;
import
java.net.URLEncoder
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.charset.StandardCharsets
;
import
java.text.SimpleDateFormat
;
import
java.text.SimpleDateFormat
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.function.BiFunction
;
/**
/**
* @Author: DouXinYu
* @Author: DouXinYu
...
@@ -29,10 +45,315 @@ import java.util.concurrent.ConcurrentHashMap;
...
@@ -29,10 +45,315 @@ import java.util.concurrent.ConcurrentHashMap;
* @Description: AP导出excel工具类
* @Description: AP导出excel工具类
*/
*/
@Slf4j
@Slf4j
@Component
public
class
ExcelUtils
{
public
class
ExcelUtils
{
//缓存样式
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
ExcelUtils
.
class
);
private
static
final
ConcurrentHashMap
<
String
,
CellStyle
>
CACHE_CELL_STYLE
=
new
ConcurrentHashMap
<>();
private
static
final
ConcurrentHashMap
<
String
,
CellStyle
>
CACHE_CELL_STYLE
=
new
ConcurrentHashMap
<>();
//表头行(默认为0)
private
static
final
int
HEADER_ROW
=
0
;
private
static
OssConfigProperties
staticOssConfig
;
//OSS配置
@Autowired
private
OssConfigProperties
ossConfig
;
@PostConstruct
public
void
init
()
{
staticOssConfig
=
this
.
ossConfig
;
}
/* ============================================================导入部分=========================================================== */
public
static
InputStream
getLocalFileInputStream
(
String
filePath
)
throws
Exception
{
// 参数校验
if
(
StringUtils
.
isBlank
(
filePath
))
{
throw
new
IllegalArgumentException
(
"本地文件路径不能为空"
);
}
File
localFile
=
new
File
(
filePath
);
// 检查文件是否存在
if
(!
localFile
.
exists
())
{
String
errorMsg
=
String
.
format
(
"本地文件不存在:%s"
,
filePath
);
log
.
error
(
errorMsg
);
throw
new
Exception
(
errorMsg
);
}
// 检查是否是文件(而非目录)
if
(!
localFile
.
isFile
())
{
String
errorMsg
=
String
.
format
(
"路径不是有效文件:%s"
,
filePath
);
log
.
error
(
errorMsg
);
throw
new
Exception
(
errorMsg
);
}
// 检查文件可读权限
if
(!
localFile
.
canRead
())
{
String
errorMsg
=
String
.
format
(
"本地文件不可读取(权限不足):%s"
,
filePath
);
log
.
error
(
errorMsg
);
throw
new
Exception
(
errorMsg
);
}
try
{
// 创建文件输入流并返回
FileInputStream
fis
=
new
FileInputStream
(
localFile
);
log
.
info
(
"成功获取本地文件输入流:{}"
,
filePath
);
return
fis
;
}
catch
(
IOException
e
)
{
String
errorMsg
=
String
.
format
(
"读取本地文件失败:%s,错误:%s"
,
filePath
,
e
.
getMessage
());
log
.
error
(
errorMsg
,
e
);
throw
new
Exception
(
errorMsg
,
e
);
}
}
/**
* 获取OSS文件输入流
* @param ossFileUrl OSS文件URL
* @return OSS文件输入流
* @throws Exception 获取OSS文件输入流失败
*/
public
static
InputStream
getOssFileInputStream
(
String
ossFileUrl
)
throws
Exception
{
// 参数校验
if
(
StringUtils
.
isBlank
(
ossFileUrl
))
{
throw
new
IllegalArgumentException
(
"OSS文件URL不能为空"
);
}
OssConfigProperties
.
Oss
oss
=
staticOssConfig
.
getOss
();
if
(
oss
==
null
||
StringUtils
.
isBlank
(
oss
.
getRegionId
())
||
StringUtils
.
isBlank
(
oss
.
getBucketName
()))
{
throw
new
IllegalStateException
(
"OSS基础配置未初始化(regionId/bucketName为空)"
);
}
// 解析OSS URL:提取文件路径(objectKey)
String
objectKey
=
parseObjectKeyFromUrl
(
ossFileUrl
,
oss
.
getWebJsLink
());
if
(
StringUtils
.
isBlank
(
objectKey
))
{
throw
new
Exception
(
"解析OSS文件路径失败:"
+
ossFileUrl
);
}
//获取STS临时凭证
AssumeRoleResponse
.
Credentials
stsCredentials
=
getStsCredentials
();
//用STS临时凭证创建OSS客户端
OSS
ossClient
=
null
;
try
{
// Endpoint
ossClient
=
new
OSSClientBuilder
().
build
(
"https://oss-"
+
oss
.
getRegionId
()
+
".aliyuncs.com"
,
// STS临时AK
stsCredentials
.
getAccessKeyId
(),
// STS临时SK
stsCredentials
.
getAccessKeySecret
(),
// STS Token
stsCredentials
.
getSecurityToken
()
);
// 校验文件是否存在
if
(!
ossClient
.
doesObjectExist
(
oss
.
getBucketName
(),
objectKey
))
{
throw
new
Exception
(
String
.
format
(
"OSS文件不存在:bucket=%s, path=%s"
,
oss
.
getBucketName
(),
objectKey
));
}
// 获取输入流(调用时注意关闭流)
return
ossClient
.
getObject
(
new
GetObjectRequest
(
oss
.
getBucketName
(),
objectKey
)).
getObjectContent
();
}
catch
(
Exception
e
)
{
log
.
error
(
"获取OSS文件失败:url={}"
,
ossFileUrl
,
e
);
throw
new
Exception
(
"OSS文件读取失败:"
+
e
.
getMessage
(),
e
);
}
finally
{
if
(
ossClient
!=
null
)
{
// 关闭客户端
ossClient
.
shutdown
();
}
}
}
/**
* 导入数据
* @param inputStream 导入文件的输入流
* @param fileName 导入文件名
* @param columnConfigs 导入列配置
* @param errorMap 错误信息
* @param dtoClass 目标DTO类型
* @return 导入数据
* @param <T> 目标DTO泛型
*/
public
static
<
T
>
List
<
T
>
readApExcelWithColumnConfig
(
InputStream
inputStream
,
String
fileName
,
List
<
ExportColumnConfig
>
columnConfigs
,
Map
<
String
,
List
<
T
>>
errorMap
,
Class
<
T
>
dtoClass
)
{
log
.
info
(
"开始导入数据(复用列配置校验),目标DTO类型:{}"
,
dtoClass
.
getSimpleName
());
// 初始化空错误列表(基础方法需要)
List
<
String
>
baseErrorList
=
new
ArrayList
<>();
// 调用基础导入方法,传入自定义行解析器
List
<
T
>
dtoList
=
readApExcel
(
inputStream
,
fileName
,
baseErrorList
,
(
row
,
rowNum
)
->
{
// 初始化当前行数据和错误信息
List
<
T
>
rowData
=
new
ArrayList
<>();
StringBuilder
rowErrorMsg
=
new
StringBuilder
();
T
dto
=
null
;
// 反射创建DTO实例
try
{
dto
=
dtoClass
.
getDeclaredConstructor
().
newInstance
();
}
catch
(
Exception
e
)
{
log
.
error
(
"创建DTO实例失败,类型:{},错误:{}"
,
dtoClass
.
getName
(),
e
.
getMessage
(),
e
);
return
null
;
}
// 逐列解析+校验(复用ExportColumnConfig规则)
for
(
int
colIndex
=
0
;
colIndex
<
columnConfigs
.
size
();
colIndex
++)
{
ExportColumnConfig
config
=
columnConfigs
.
get
(
colIndex
);
String
fieldName
=
config
.
getFieldName
();
Cell
cell
=
row
.
getCell
(
colIndex
);
String
cellValue
=
getCellStringValue
(
cell
);
// 记录原始行数据,用于错误Map
rowData
.
add
((
T
)
cellValue
);
// 字段赋值(兼容数值/字符串类型)
try
{
if
(
StringUtils
.
isNotBlank
(
cellValue
))
{
if
(
config
.
isNumberValidation
())
{
// 数值类型字段:转换为BigDecimal赋值
Double
numericValue
=
Double
.
parseDouble
(
cellValue
);
BeanUtil
.
setProperty
(
dto
,
fieldName
,
new
BigDecimal
(
numericValue
));
}
else
{
// 字符串类型字段:直接赋值
BeanUtil
.
setProperty
(
dto
,
fieldName
,
cellValue
);
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"字段赋值失败,字段名:{},值:{},错误:{}"
,
fieldName
,
cellValue
,
e
.
getMessage
());
}
// 原有校验规则(数值范围/枚举值)
if
(
config
.
isNumberValidation
()
&&
StringUtils
.
isNotBlank
(
cellValue
))
{
// 数值范围校验
try
{
BigDecimal
value
=
new
BigDecimal
(
cellValue
);
if
(
value
.
compareTo
(
new
BigDecimal
(
config
.
getValidationNumberMin
()))
<
0
||
value
.
compareTo
(
new
BigDecimal
(
config
.
getValidationNumberMax
()))
>
0
)
{
rowErrorMsg
.
append
(
config
.
getValidationErrorMsg
()).
append
(
";"
);
}
}
catch
(
Exception
e
)
{
rowErrorMsg
.
append
(
config
.
getValidationErrorMsg
()).
append
(
";"
);
}
}
else
if
(
CollectionUtils
.
isNotEmpty
(
config
.
getValidationValidOptions
())
&&
StringUtils
.
isNotBlank
(
cellValue
))
{
// 枚举值校验
if
(!
config
.
getValidationValidOptions
().
contains
(
cellValue
))
{
rowErrorMsg
.
append
(
config
.
getValidationErrorMsg
()).
append
(
";"
);
}
}
}
// 收集错误
if
(
rowErrorMsg
.
length
()
>
0
)
{
String
errorKey
=
rowErrorMsg
.
toString
();
errorMap
.
computeIfAbsent
(
errorKey
,
k
->
new
ArrayList
<>()).
addAll
(
rowData
);
// 给DTO设置错误信息(需DTO包含errorMsg字段)
try
{
BeanUtil
.
setProperty
(
dto
,
"errorMsg"
,
errorKey
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"DTO[{}]无errorMsg字段,跳过错误信息赋值"
,
dtoClass
.
getSimpleName
());
}
}
else
{
// 即使没有错误,也要确保errorMsg字段有默认值
try
{
BeanUtil
.
setProperty
(
dto
,
"errorMsg"
,
""
);
}
catch
(
Exception
e
)
{
log
.
debug
(
"DTO[{}]无errorMsg字段或设置默认值失败"
,
dtoClass
.
getSimpleName
());
}
}
// 返回DTO(即使有错误也返回)
return
dto
;
});
// 打印基础错误(如文件格式、空Sheet)
if
(
CollectionUtils
.
isNotEmpty
(
baseErrorList
))
{
log
.
warn
(
"导入基础校验错误:{}"
,
String
.
join
(
";"
,
baseErrorList
));
throw
new
RuntimeException
(
String
.
join
(
";"
,
baseErrorList
));
}
return
dtoList
;
}
/**
* 基础AP Excel导入方法(通用行解析,无业务耦合)
* @param inputStream Excel文件流
* @param fileName 文件名(校验格式)
* @param errorList 基础错误列表(文件格式、空Sheet等)
* @param rowParser 行解析器(自定义每行解析逻辑)
* @param <T> 解析结果类型
* @return 解析后的列表
*/
private
static
<
T
>
List
<
T
>
readApExcel
(
InputStream
inputStream
,
String
fileName
,
List
<
String
>
errorList
,
BiFunction
<
Row
,
Integer
,
T
>
rowParser
)
{
log
.
info
(
"开始基础Excel导入解析,文件名:{}"
,
fileName
);
List
<
T
>
dataList
=
new
ArrayList
<>();
Workbook
workbook
=
null
;
try
{
// 1. 校验文件格式
if
(
fileName
==
null
||
(!
fileName
.
endsWith
(
".xls"
)
&&
!
fileName
.
endsWith
(
".xlsx"
)))
{
errorList
.
add
(
"文件格式错误,仅支持.xls和.xlsx"
);
return
dataList
;
}
// 2. 创建Workbook
if
(
fileName
.
endsWith
(
".xls"
))
{
workbook
=
new
HSSFWorkbook
(
inputStream
);
}
else
{
workbook
=
new
XSSFWorkbook
(
inputStream
);
}
// 3. 校验Sheet
Sheet
sheet
=
workbook
.
getSheetAt
(
0
);
if
(
sheet
==
null
)
{
errorList
.
add
(
"获取sheet为空,导入失败,请检查文件!"
);
return
dataList
;
}
int
lastRowNum
=
sheet
.
getLastRowNum
();
if
(
lastRowNum
<=
HEADER_ROW
)
{
errorList
.
add
(
"获取数据行数小于等于表头行数,文件内无内容,导入失败,请检查文件!"
);
return
dataList
;
}
// 4. 逐行解析
for
(
int
rowIndex
=
HEADER_ROW
+
1
;
rowIndex
<=
lastRowNum
;
rowIndex
++)
{
Row
row
=
sheet
.
getRow
(
rowIndex
);
if
(
row
==
null
)
{
// 调整:移除行号,仅保留纯错误提示
errorList
.
add
(
"数据行为空,跳过处理"
);
continue
;
}
T
rowData
=
rowParser
.
apply
(
row
,
rowIndex
+
1
);
if
(
rowData
!=
null
)
{
dataList
.
add
(
rowData
);
}
}
}
catch
(
IOException
e
)
{
log
.
error
(
"导入Excel文件失败:{}"
,
e
.
getMessage
(),
e
);
errorList
.
add
(
"文件读取失败:"
+
e
.
getMessage
());
}
finally
{
// 5. 关闭流
try
{
if
(
workbook
!=
null
)
{
workbook
.
close
();
}
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
}
}
catch
(
IOException
e
)
{
log
.
error
(
"Excel流关闭异常"
,
e
);
}
}
return
dataList
;
}
/* ============================================================导出部分=========================================================== */
/**
/**
* 将Excel字节数组导出为文件(写入Http响应流)
* 将Excel字节数组导出为文件(写入Http响应流)
...
@@ -70,13 +391,13 @@ public class ExcelUtils {
...
@@ -70,13 +391,13 @@ public class ExcelUtils {
outputStream
.
write
(
excelBytes
);
outputStream
.
write
(
excelBytes
);
outputStream
.
flush
();
outputStream
.
flush
();
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
ger
.
error
(
"写入Excel响应流失败:{}"
,
e
.
getMessage
(),
e
);
log
.
error
(
"写入Excel响应流失败:{}"
,
e
.
getMessage
(),
e
);
throw
new
Exception
(
"导出Excel失败:"
+
e
.
getMessage
());
throw
new
Exception
(
"导出Excel失败:"
+
e
.
getMessage
());
}
}
}
}
/**
/**
* 生成Excel
文件
* 生成Excel
字节流
*
*
* @param exportColumnConfigList 表头配置
* @param exportColumnConfigList 表头配置
* @param dataList 数据列表
* @param dataList 数据列表
...
@@ -112,7 +433,7 @@ public class ExcelUtils {
...
@@ -112,7 +433,7 @@ public class ExcelUtils {
for
(
int
i
=
0
;
i
<
exportColumnConfigList
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
exportColumnConfigList
.
size
();
i
++)
{
ExportColumnConfig
config
=
exportColumnConfigList
.
get
(
i
);
ExportColumnConfig
config
=
exportColumnConfigList
.
get
(
i
);
// 给列配置数据验证(跳过表头)
// 给列配置数据验证(跳过表头)
ExcelStyleUtils
.
addColumnDataValidation
(
sheet
,
i
,
config
);
ExcelStyleUtils
.
addColumnDataValidation
(
sheet
,
i
,
config
);
if
(
isColumnProtected
(
config
))
{
if
(
isColumnProtected
(
config
))
{
// 为该列所有数据行添加只读验证
// 为该列所有数据行添加只读验证
// 从第1行开始(跳过表头)
// 从第1行开始(跳过表头)
...
@@ -130,12 +451,14 @@ public class ExcelUtils {
...
@@ -130,12 +451,14 @@ public class ExcelUtils {
}
}
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
log
ger
.
error
(
"生成Excel文件失败:{}"
,
e
.
getMessage
(),
e
);
log
.
error
(
"生成Excel文件失败:{}"
,
e
.
getMessage
(),
e
);
throw
new
IOException
(
"生成Excel文件失败:"
+
e
.
getMessage
());
throw
new
IOException
(
"生成Excel文件失败:"
+
e
.
getMessage
());
}
}
}
}
/* ============================================================导出工具方法=========================================================== */
/**
/**
* 创建工作簿
* 创建工作簿
*
*
...
@@ -188,11 +511,11 @@ public class ExcelUtils {
...
@@ -188,11 +511,11 @@ public class ExcelUtils {
// 为表头行添加只读验证(防止用户修改表头)
// 为表头行添加只读验证(防止用户修改表头)
for
(
int
i
=
0
;
i
<
exportColumnConfigList
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
exportColumnConfigList
.
size
();
i
++)
{
ExportColumnConfig
exportColumnConfig
=
exportColumnConfigList
.
get
(
i
);
ExportColumnConfig
exportColumnConfig
=
exportColumnConfigList
.
get
(
i
);
if
(
exportColumnConfig
.
getValidationPromptTitle
()
!=
null
&&
exportColumnConfig
.
getValidationPromptMsg
()
!=
null
){
if
(
exportColumnConfig
.
getValidationPromptTitle
()
!=
null
&&
exportColumnConfig
.
getValidationPromptMsg
()
!=
null
)
{
addHeaderPromptValidation
(
sheet
,
i
,
exportColumnConfig
);
addHeaderPromptValidation
(
sheet
,
i
,
exportColumnConfig
);
}
}
// 只保护第0行(表头行)
// 只保护第0行(表头行)
addReadOnlyValidation
(
sheet
,
i
,
0
,
0
);
addReadOnlyValidation
(
sheet
,
i
,
HEADER_ROW
,
HEADER_ROW
);
}
}
}
}
...
@@ -274,7 +597,7 @@ public class ExcelUtils {
...
@@ -274,7 +597,7 @@ public class ExcelUtils {
DataValidationConstraint
constraint
=
helper
.
createCustomConstraint
(
"FALSE"
);
DataValidationConstraint
constraint
=
helper
.
createCustomConstraint
(
"FALSE"
);
// 仅对表头行(第0行)应用
// 仅对表头行(第0行)应用
CellRangeAddressList
addressList
=
new
CellRangeAddressList
(
0
,
0
,
columnIndex
,
columnIndex
);
CellRangeAddressList
addressList
=
new
CellRangeAddressList
(
HEADER_ROW
,
HEADER_ROW
,
columnIndex
,
columnIndex
);
DataValidation
validation
=
helper
.
createValidation
(
constraint
,
addressList
);
DataValidation
validation
=
helper
.
createValidation
(
constraint
,
addressList
);
...
@@ -306,7 +629,7 @@ public class ExcelUtils {
...
@@ -306,7 +629,7 @@ public class ExcelUtils {
// 使用 BeanUtils 获取属性值
// 使用 BeanUtils 获取属性值
return
BeanUtil
.
getProperty
(
data
,
fieldName
);
return
BeanUtil
.
getProperty
(
data
,
fieldName
);
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
log
ger
.
error
(
"获取字段值失败:{}"
,
fieldName
,
e
);
log
.
error
(
"获取字段值失败:{}"
,
fieldName
,
e
);
// 空值兜底,避免崩溃
// 空值兜底,避免崩溃
return
""
;
return
""
;
}
}
...
@@ -414,19 +737,19 @@ public class ExcelUtils {
...
@@ -414,19 +737,19 @@ public class ExcelUtils {
return
""
;
return
""
;
}
}
// 替换中文括号及其内容为换行形式
// 替换中文括号及其内容为换行形式
String
result
;
String
result
;
if
(
originalText
.
contains
(
"("
)
&&
originalText
.
contains
(
")"
))
{
if
(
originalText
.
contains
(
"("
)
&&
originalText
.
contains
(
")"
))
{
// 中文括号替换为换行
// 中文括号替换为换行
result
=
originalText
.
replaceAll
(
"((.*?))"
,
"\n$1"
);
result
=
originalText
.
replaceAll
(
"((.*?))"
,
"\n$1"
);
}
else
if
(
originalText
.
contains
(
"("
)
&&
originalText
.
contains
(
")"
)){
}
else
if
(
originalText
.
contains
(
"("
)
&&
originalText
.
contains
(
")"
))
{
// 英文括号替换为换行
// 英文括号替换为换行
result
=
originalText
.
replaceAll
(
"\\((.*?)\\)"
,
"\n$1"
);
result
=
originalText
.
replaceAll
(
"\\((.*?)\\)"
,
"\n$1"
);
}
else
if
(
originalText
.
contains
(
"-"
))
{
}
else
if
(
originalText
.
contains
(
"-"
))
{
// 横线替换为换行(内容)
// 横线替换为换行(内容)
result
=
originalText
.
replaceAll
(
"-(.+)"
,
"\n($1)"
);
result
=
originalText
.
replaceAll
(
"-(.+)"
,
"\n($1)"
);
}
else
if
(
originalText
.
contains
(
"_"
))
{
}
else
if
(
originalText
.
contains
(
"_"
))
{
result
=
originalText
.
replaceAll
(
"_(.+)"
,
"\n($1)"
);
result
=
originalText
.
replaceAll
(
"_(.+)"
,
"\n($1)"
);
}
else
{
}
else
{
result
=
originalText
;
result
=
originalText
;
}
}
...
@@ -469,5 +792,152 @@ public class ExcelUtils {
...
@@ -469,5 +792,152 @@ public class ExcelUtils {
return
config
.
getStyle
()
==
ExcelStyleUtils
.
ExcelStyle
.
UNMODIFIABLE
||
config
.
getStyle
()
==
ExcelStyleUtils
.
ExcelStyle
.
CHANGE_TEXT_STYLE
;
return
config
.
getStyle
()
==
ExcelStyleUtils
.
ExcelStyle
.
UNMODIFIABLE
||
config
.
getStyle
()
==
ExcelStyleUtils
.
ExcelStyle
.
CHANGE_TEXT_STYLE
;
}
}
/* ================================================导入工具方法=================================================== */
/**
* 获取单元格内容
*
* @param cell 单元格
* @return 单元格内容
*/
public
static
String
getCellStringValue
(
Cell
cell
)
{
if
(
cell
==
null
)
{
return
""
;
}
CellType
cellType
=
cell
.
getCellType
();
switch
(
cellType
)
{
case
STRING:
return
cell
.
getStringCellValue
().
trim
();
case
NUMERIC:
// 处理日期/数字
if
(
DateUtil
.
isCellDateFormatted
(
cell
))
{
return
cell
.
getDateCellValue
().
toString
();
}
else
{
// 避免科学计数法,去除末尾的.0
return
String
.
valueOf
(
cell
.
getNumericCellValue
()).
replaceAll
(
"\\.0*$"
,
""
);
}
case
BOOLEAN:
return
String
.
valueOf
(
cell
.
getBooleanCellValue
());
case
FORMULA:
try
{
return
String
.
valueOf
(
cell
.
getNumericCellValue
()).
replaceAll
(
"\\.0*$"
,
""
);
}
catch
(
IllegalStateException
e
)
{
return
cell
.
getStringCellValue
().
trim
();
}
default
:
return
""
;
}
}
/**
* 判断单元格是否有值(与导出时的条件样式判断逻辑完全一致)
* @param cell 单元格
* @return true=有值,false=无值
*/
private
static
boolean
isCellHasValue
(
Cell
cell
)
{
if
(
cell
==
null
)
{
return
false
;
}
switch
(
cell
.
getCellType
())
{
case
STRING:
return
StringUtils
.
isNotBlank
(
cell
.
getStringCellValue
().
trim
());
case
NUMERIC:
// 数值类型无论值是多少都算有值
return
true
;
case
BOOLEAN:
// 布尔类型算有值
return
true
;
default
:
return
false
;
}
}
/**
* 获取STS临时凭证
* @return STS临时凭证
* @throws ClientException
*/
private
static
AssumeRoleResponse
.
Credentials
getStsCredentials
()
throws
ClientException
{
OssConfigProperties
.
Oss
oss
=
staticOssConfig
.
getOss
();
// 构建STS客户端
IClientProfile
profile
=
DefaultProfile
.
getProfile
(
oss
.
getRegionId
(),
staticOssConfig
.
getAccessKeyId
(),
staticOssConfig
.
getAccessKeySecret
()
);
DefaultAcsClient
client
=
new
DefaultAcsClient
(
profile
);
// 构建STS请求
AssumeRoleRequest
request
=
new
AssumeRoleRequest
();
// POST方式发送请求
request
.
setMethod
(
MethodType
.
POST
);
// 你的STS角色ARN
request
.
setRoleArn
(
oss
.
getStsRoleArm
());
request
.
setRoleSessionName
(
oss
.
getSessionName
());
// 获取STS响应
AssumeRoleResponse
response
=
client
.
getAcsResponse
(
request
);
AssumeRoleResponse
.
Credentials
credentials
=
response
.
getCredentials
();
if
(
credentials
==
null
)
{
throw
new
ClientException
(
"STS临时凭证获取失败"
);
}
log
.
info
(
"STS临时凭证获取成功,过期时间:{}"
,
credentials
.
getExpiration
());
return
credentials
;
}
/**
* 从OSS文件URL中解析出ObjectKey
* @param ossFileUrl OSS文件URL
* @param ossDomain OSS域名
* @return ObjectKey
*/
private
static
String
parseObjectKeyFromUrl
(
String
ossFileUrl
,
String
ossDomain
)
{
if
(
StringUtils
.
isBlank
(
ossFileUrl
)
||
StringUtils
.
isBlank
(
ossDomain
))
{
return
null
;
}
String
domainPrefix
=
"//"
+
ossDomain
+
"/"
;
if
(
ossFileUrl
.
contains
(
domainPrefix
))
{
return
ossFileUrl
.
split
(
domainPrefix
)[
1
];
}
// 兼容直接传入objectKey的场景(如path/file.xlsx)
if
(!
ossFileUrl
.
startsWith
(
"http"
))
{
return
ossFileUrl
;
}
return
null
;
}
/**
* 从OSS文件URL中解析出文件名
* @param ossUrl OSS文件URL
* @return 文件名
*/
public
static
String
parseFileNameFromOssUrl
(
String
ossUrl
)
{
//参数校验
if
(
StringUtils
.
isBlank
(
ossUrl
)
||
!
ossUrl
.
contains
(
"oss-"
)
||
!
ossUrl
.
contains
(
".aliyuncs.com"
))
{
log
.
warn
(
"非有效阿里云OSS URL,使用通用默认文件名:{}"
,
ossUrl
);
// 通用默认名,不绑定具体业务
return
"Excel导入文件"
;
}
// 解析文件名:按"/"分割取URL最后一段
String
[]
urlParts
=
ossUrl
.
split
(
"/"
);
String
fileName
=
urlParts
[
urlParts
.
length
-
1
];
// - 解析结果为空 → 通用前缀+时间戳+默认后缀
// - 无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
);
return
fileName
;
}
}
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论