Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
E
excel-export-service
概览
概览
详情
活动
周期分析
版本库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
export-service
excel-export-service
Commits
f30ace05
提交
f30ace05
authored
9月 28, 2025
作者:
lixiaojuan
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
打包
上级
96886500
隐藏空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
433 行增加
和
103 行删除
+433
-103
Dockerfile
build/Dockerfile
+39
-0
build.sh
build/build.sh
+78
-0
env.prod
build/env/env.prod
+33
-0
env.test
build/env/env.test
+2
-2
run.sh
build/run.sh
+82
-0
pom.xml
pom.xml
+2
-2
ExcelExportService.java
...m/zzsn/excelexportservice/service/ExcelExportService.java
+196
-98
application.yml
src/main/resources/application.yml
+1
-1
没有找到文件。
build/Dockerfile
0 → 100644
浏览文件 @
f30ace05
# 构建阶段
FROM
swr.cn-southwest-2.myhuaweicloud.com/wd/maven:3.8.5-openjdk-17 AS builder
USER
root
WORKDIR
/workspace
COPY
. .
RUN
mvn clean install
-DskipTests
# 运行时阶段:直接用精简版 openjdk 镜像
FROM
swr.cn-southwest-2.myhuaweicloud.com/wd/openjdk:17-fontconfig
MAINTAINER
lixingyu
USER
root
WORKDIR
/workspace
# 复制 jar 包
COPY
--from=builder /workspace/target/*.jar /workspace/app.jar
# 环境变量
ARG
ENV_NAME
ARG
NACOS_SERVER
ARG
NACOS_NAMESPACE
ARG
NACOS_REGISTER_IP
ARG
PORT
ENV
APP_OPTS1="-Dspring.profiles.active=${ENV_NAME}"
ENV
APP_OPTS2="-Djasypt.encryptor.password=1@wdLkj90#chMsdzxA%2024"
ENV
APP_OPTS3="-Dfile.encoding=utf-8"
ENV
APP_OPTS4="-Duser.timezone=Asia/Shanghai"
# 无头模式避免 AWT 调用
ENV
APP_OPTS9="-Djava.awt.headless=true"
ENV
APP_OPTS5="-DNACOS_SERVER=${NACOS_SERVER}"
ENV
APP_OPTS6="-DNACOS_NAMESPACE=${NACOS_NAMESPACE}"
ENV
APP_OPTS7="-DNACOS_REGISTER_IP=${NACOS_REGISTER_IP}"
ENV
JVM_OPTS="-Xmx2024M -Xms256M"
EXPOSE
${PORT}
ENTRYPOINT
["sh","-c","java $APP_OPTS1 $APP_OPTS2 $APP_OPTS3 $APP_OPTS4 $APP_OPTS9 $APP_OPTS5 $APP_OPTS6 $APP_OPTS7 $JVM_OPTS -jar /workspace/app.jar"]
\ No newline at end of file
build/build.sh
0 → 100644
浏览文件 @
f30ace05
#!/bin/bash
# build.sh - 功能包括:镜像构建,推送镜像,处理none镜像。
# author: lixingyu
# 使用方法:
# $ ./build.sh <env_name> <build_timestamp>
# 参数:
# env_name:环境变量 build_timestamp:构建时间戳
set
-eu
# 获取脚本的绝对路径(包括文件名)
script_path
=
$(
readlink
-f
"
$0
"
)
# 获取脚本所在的目录的绝对路径
script_dir
=
$(
dirname
"
$script_path
"
)
# 函数定义
function
print_usage
{
# 打印使用说明
sed
-n
'2,7p'
"
$0
"
}
function
handle
{
local
env_name
=
$1
local
build_timestamp
=
$2
echo
"执行的环境变量:
${
env_name
}
"
echo
"执行的构建时间戳:
${
build_timestamp
}
"
if
[
!
-f
"
$script_dir
/env/env.
${
env_name
}
"
]
;
then
echo
"错误: 文件 'env.
${
env_name
}
' 不存在."
>
&2
exit
1
fi
.
$script_dir
/env/env.
${
env_name
}
local
username
=
"
${
repo_username
}
"
local
passwd
=
"
${
repo_passwd
}
"
local
name
=
"
${
svc_name
}
-
${
svc_env
}
"
local
version
=
${
version
}
-
${
build_timestamp
}
local
internal_ip
=
"
${
internal_ip
}
"
echo
"构建名称:
${
name
}
"
echo
"构建端口:
${
svc_port
}
"
echo
"部署节点:
${
internal_ip
}
"
echo
"构建版本:
${
version
}
"
echo
"推送仓库:
${
domain
}
/
${
namespace
}
"
echo
"完整镜像:
${
domain
}
/
${
namespace
}
/
${
name
}
:
${
version
}
"
echo
"---构建镜像---"
docker build
--build-arg
NACOS_REGISTER_IP
=
${
internal_ip
}
--build-arg
NACOS_SERVER
=
${
nacos_server
}
--build-arg
NACOS_NAMESPACE
=
${
nacos_namespace
}
\
--build-arg
ENV_NAME
=
${
svc_env
}
--build-arg
PORT
=
${
svc_port
}
-f
./build/Dockerfile
-t
${
domain
}
/
${
namespace
}
/
${
name
}
:
${
version
}
.
echo
"---推送镜像---"
docker login
-u
=
$username
-p
=
${
passwd
}
${
domain
}
docker push
${
domain
}
/
${
namespace
}
/
${
name
}
:
${
version
}
echo
"---清理none镜像---"
docker container prune
-f
docker image prune
-af
}
# 主程序入口点
function
main
{
if
[
"$#"
-ne
2
]
;
then
print_usage
echo
"错误: 需要提供两个个参数 <env_name> <build_timestamp>"
>
&2
exit
1
fi
local
env_name
=
$1
local
build_timestamp
=
$2
handle
"
${
env_name
}
"
"
${
build_timestamp
}
"
}
# 错误处理
trap
'echo 发生了错误,脚本中断.'
ERR
# 调用主函数
main
"
$@
"
build/env/env.prod
0 → 100644
浏览文件 @
f30ace05
# 镜像仓库
## 版本 T1.0.0: T为测试版,R为稳定版
version="T1.0.0" # 可自定义
## 镜像仓库地址
domain="swr.cn-southwest-2.myhuaweicloud.com"
## 镜像分组,按部门区分
namespace="wd"
# 服务配置
## 部署节点的内网IP
internal_ip=server-1-95-67-224.ciglobal.cn
## 数据挂载根目录
root_dir="/zzsn"
## 需根据日志配置填写,比如:logback-spring.xml
svc_logs="/workspace/logs"
## 环境变量 test or prod
svc_env="prod"
## 服务名
svc_name="excel-export-service"
## 服务端口
svc_port="9120"
# nacos配置
## 往nacos注册IP,配置文件使用变量 NACOS_REGISTER_IP
## nacos服务地址, 配置文件使用 NACOS_SERVER
nacos_server="server-1-95-77-159.ciglobal.cn:8848"
## nacos命名空间, 配置文件使用 NACOS_NAMESPACE
nacos_namespace="smartProd"
# 资源限制
## cpu
limit_cpu=1.0
limit_mem=2g
build/env/env.test
浏览文件 @
f30ace05
...
@@ -8,7 +8,7 @@ namespace="wd"
...
@@ -8,7 +8,7 @@ namespace="wd"
# 服务配置
# 服务配置
## 部署节点的内网IP
## 部署节点的内网IP
internal_ip
=
server
-
1
-
95
-
14
-
24
.
ciglobal
.
cn
internal_ip
=
server
-
1
-
95
-
77
-
159
.
ciglobal
.
cn
## 数据挂载根目录
## 数据挂载根目录
root_dir
=
"/zzsn"
root_dir
=
"/zzsn"
## 需根据日志配置填写,比如:logback-spring.xml
## 需根据日志配置填写,比如:logback-spring.xml
...
@@ -18,7 +18,7 @@ svc_env="test"
...
@@ -18,7 +18,7 @@ svc_env="test"
## 服务名
## 服务名
svc_name
=
"excel-export-service"
svc_name
=
"excel-export-service"
## 服务端口
## 服务端口
svc_port
=
"
8089
"
svc_port
=
"
9120
"
# nacos配置
# nacos配置
## 往nacos注册IP,配置文件使用变量 NACOS_REGISTER_IP
## 往nacos注册IP,配置文件使用变量 NACOS_REGISTER_IP
...
...
build/run.sh
0 → 100644
浏览文件 @
f30ace05
#!/bin/bash
# run.sh - 功能包括:运行容器,处理none镜像。
# author: lixingyu
# 使用方法:
# $ ./run.sh <env_name> <build_timestamp>
# 参数:
# env_name:环境变量 build_timestamp:构建时间戳
set
-eu
# 获取脚本的绝对路径(包括文件名)
script_path
=
$(
readlink
-f
"
$0
"
)
# 获取脚本所在的目录的绝对路径
script_dir
=
$(
dirname
"
$script_path
"
)
# 函数定义
function
print_usage
{
# 打印使用说明
sed
-n
'2,7p'
"
$0
"
}
function
handle
{
local
env_name
=
$1
local
build_timestamp
=
$2
echo
"执行的环境变量:
${
env_name
}
"
echo
"执行的构建时间戳:
${
build_timestamp
}
"
if
[
!
-f
"
$script_dir
/env/env.
${
env_name
}
"
]
;
then
echo
"错误: 文件 'env.
${
env_name
}
' 不存在."
>
&2
exit
1
fi
.
$script_dir
/env/env.
${
env_name
}
local
dir
=
"
${
root_dir
}
/
${
svc_name
}
/
${
svc_env
}
"
local
name
=
"
${
svc_name
}
-
${
svc_env
}
"
local
version
=
${
version
}
-
${
build_timestamp
}
local
internal_ip
=
"
${
internal_ip
}
"
echo
"---运行容器:
${
name
}
:
${
version
}
---"
# 构建 JSON 数据
json_data
=
"{
\
\"
container_name
\"
:
\"
$name
\"
,
\
\"
image_version
\"
:
\"
$domain
/
$namespace
/
$name
:
$version
\"
,
\
\"
ports
\"
: [
\"
$svc_port
:
$svc_port
\"
],
\
\"
mount_infos
\"
: [
\"
/etc/localtime:/etc/localtime:ro
\"
,
\"
$dir
/logs:
$svc_logs
\"
],
\
\"
cpu_count
\"
:
$limit_cpu
,
\
\"
memory_limit
\"
:
\"
$limit_mem
\"
,
\
\"
ulimit
\"
:
\"
nofile=65535:65535
\"
\
}"
# 执行 curl 请求,并将响应结果存储在 response 变量中
response
=
$(
curl
-s
-X
POST
-H
"Content-Type: application/json"
-H
"X-API-Key: uOyKfp20pdM3MFhr3KAQBoe1UHCaZLUeeLephB57MPvGXTY05Eis5eaxta6fEtpa"
-d
"
$json_data
"
"http://
$internal_ip
:10080/start-container"
)
echo
"响应结果:
${
response
}
"
# 检查响应中是否包含 "succeed"
if
echo
"
$response
"
|
grep
-q
"successfully"
;
then
echo
"部署成功"
exit
0
else
echo
"部署失败"
exit
1
fi
}
# 主程序入口点
function
main
{
if
[
"$#"
-ne
2
]
;
then
print_usage
echo
"错误: 需要提供两个个参数 <env_name> <build_timestamp>"
>
&2
exit
1
fi
local
env_name
=
$1
local
build_timestamp
=
$2
handle
"
${
env_name
}
"
"
${
build_timestamp
}
"
}
# 错误处理
trap
'echo 发生了错误,脚本中断.'
ERR
# 调用主函数
main
"
$@
"
pom.xml
浏览文件 @
f30ace05
...
@@ -121,7 +121,7 @@
...
@@ -121,7 +121,7 @@
<dependency>
<dependency>
<groupId>
org.apache.poi
</groupId>
<groupId>
org.apache.poi
</groupId>
<artifactId>
poi
</artifactId>
<artifactId>
poi
</artifactId>
<version>
4.1.2
</version>
<version>
5.4.1
</version>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
commons-lang
</groupId>
<groupId>
commons-lang
</groupId>
...
@@ -131,7 +131,7 @@
...
@@ -131,7 +131,7 @@
<dependency>
<dependency>
<groupId>
org.apache.poi
</groupId>
<groupId>
org.apache.poi
</groupId>
<artifactId>
poi-ooxml
</artifactId>
<artifactId>
poi-ooxml
</artifactId>
<version>
4.1.2
</version>
<version>
5.4.1
</version>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
com.alibaba.fastjson2
</groupId>
<groupId>
com.alibaba.fastjson2
</groupId>
...
...
src/main/java/com/zzsn/excelexportservice/service/ExcelExportService.java
浏览文件 @
f30ace05
...
@@ -21,6 +21,7 @@ import java.io.IOException;
...
@@ -21,6 +21,7 @@ import java.io.IOException;
import
java.net.URLEncoder
;
import
java.net.URLEncoder
;
import
java.util.*
;
import
java.util.*
;
import
com.zzsn.excelexportservice.dto.ExportDataResponse.Header
;
import
com.zzsn.excelexportservice.dto.ExportDataResponse.Header
;
/**
/**
* @Author: lxj
* @Author: lxj
* @Date: 2025/9/18 11:07
* @Date: 2025/9/18 11:07
...
@@ -29,6 +30,12 @@ import com.zzsn.excelexportservice.dto.ExportDataResponse.Header;
...
@@ -29,6 +30,12 @@ import com.zzsn.excelexportservice.dto.ExportDataResponse.Header;
@Slf4j
@Slf4j
@RequiredArgsConstructor
@RequiredArgsConstructor
public
class
ExcelExportService
{
public
class
ExcelExportService
{
static
{
// 设置无头模式,避免字体配置问题
System
.
setProperty
(
"java.awt.headless"
,
"true"
);
}
@Resource
@Resource
private
CmsSearch
cmsSearch
;
private
CmsSearch
cmsSearch
;
...
@@ -39,66 +46,124 @@ public class ExcelExportService {
...
@@ -39,66 +46,124 @@ public class ExcelExportService {
sendError
(
response
,
"导出失败: 入参为空"
);
sendError
(
response
,
"导出失败: 入参为空"
);
return
;
return
;
}
}
SXSSFWorkbook
workbook
=
new
SXSSFWorkbook
(
1000
);
workbook
.
setCompressTempFiles
(
true
);
for
(
ExportReq
exportReq
:
exportReqList
)
{
SXSSFWorkbook
workbook
=
null
;
ExportDataResponse
resp
=
null
;
try
{
String
serviceName
=
exportReq
.
getServiceName
();
workbook
=
new
SXSSFWorkbook
(
1000
);
Map
<
String
,
Object
>
queryParams
=
exportReq
.
getQueryParams
();
workbook
.
setCompressTempFiles
(
true
);
ExportStrategy
strategy
=
exportStrategyFactory
.
getStrategy
(
serviceName
,
exportReq
.
getApiPath
());
for
(
ExportReq
exportReq
:
exportReqList
)
{
if
(
strategy
==
null
)
{
ExportDataResponse
resp
=
null
;
log
.
error
(
"No strategy found for service: {}, path: {}"
,
serviceName
,
exportReq
.
getApiPath
());
String
serviceName
=
exportReq
.
getServiceName
();
sendError
(
response
,
"导出失败: 未找到匹配的服务策略"
);
Map
<
String
,
Object
>
queryParams
=
exportReq
.
getQueryParams
()
!=
null
return
;
?
exportReq
.
getQueryParams
()
}
:
new
HashMap
<>();
ExportStrategy
strategy
=
exportStrategyFactory
.
getStrategy
(
serviceName
,
exportReq
.
getApiPath
());
if
(
strategy
==
null
)
{
log
.
error
(
"No strategy found for service: {}, path: {}"
,
serviceName
,
exportReq
.
getApiPath
());
sendError
(
response
,
"导出失败: 未找到匹配的服务策略"
);
return
;
}
resp
=
strategy
.
execute
(
request
,
queryParams
,
exportReq
);
resp
=
strategy
.
execute
(
request
,
queryParams
,
exportReq
);
if
(
resp
==
null
||
resp
.
getCode
()
!=
0
)
{
if
(
resp
==
null
||
resp
.
getCode
()
!=
0
)
{
log
.
error
(
"导出数据失败, serviceName: {}, apiPath: {}, errorMsg: {}"
,
log
.
error
(
"导出数据失败, serviceName: {}, apiPath: {}, errorMsg: {}"
,
serviceName
,
exportReq
.
getApiPath
(),
resp
!=
null
?
resp
.
getMsg
()
:
"null"
);
serviceName
,
exportReq
.
getApiPath
(),
resp
!=
null
?
resp
.
getMsg
()
:
"null"
);
sendError
(
response
,
resp
!=
null
?
resp
.
getMsg
()
:
"导出失败: 未知错误"
);
sendError
(
response
,
resp
!=
null
?
resp
.
getMsg
()
:
"导出失败: 未知错误"
);
return
;
return
;
}
}
// 创建 sheet
// 安全创建 sheet
Sheet
sheet
=
workbook
.
createSheet
(
String
sheetName
=
generateSafeSheetName
(
StringUtils
.
isNotBlank
(
exportReq
.
getSheetName
())
StringUtils
.
isNotBlank
(
exportReq
.
getSheetName
())
?
exportReq
.
getSheetName
()
?
exportReq
.
getSheetName
()
:
"Sheet_"
+
serviceName
);
:
"Sheet_"
+
serviceName
);
Sheet
sheet
=
workbook
.
createSheet
(
sheetName
);
int
startRow
=
0
;
int
endCol
=
13
;
// 判断是否有图片
if
(
StringUtils
.
isNotBlank
(
exportReq
.
getBase64Img
()))
{
log
.
info
(
"开始插入图片------->"
+
System
.
currentTimeMillis
());
drawImg
(
workbook
,
sheet
,
exportReq
.
getBase64Img
(),
startRow
,
endCol
);
startRow
++;
// 图片占用一行,从下一行开始写标题
}
int
startRow
=
0
;
List
<
Map
<
String
,
Object
>>
dataList
=
resp
.
getDataList
()
!=
null
int
endCol
=
13
;
?
resp
.
getDataList
()
:
new
ArrayList
<>();
List
<
Header
>
headers
=
resp
.
getHeaders
()
!=
null
?
resp
.
getHeaders
()
:
new
ArrayList
<>();
// 判断是否有图片
// 写标题
if
(
StringUtils
.
isNotBlank
(
exportReq
.
getBase64Img
()))
{
log
.
info
(
"开始插入标题------->"
+
System
.
currentTimeMillis
());
log
.
info
(
"开始插入图片------->"
+
System
.
currentTimeMillis
());
handlerExcelTitle
(
workbook
,
sheet
,
startRow
,
headers
);
drawImg
(
workbook
,
sheet
,
exportReq
.
getBase64Img
(),
startRow
,
endCol
);
startRow
++;
// 图片占用一行,从下一行开始写标题
}
List
<
Map
<
String
,
Object
>>
dataList
=
resp
.
getDataList
();
// 写数据
List
<
Header
>
headers
=
resp
.
getHeaders
();
log
.
info
(
"开始写入数据------->"
+
System
.
currentTimeMillis
());
handlerExcelData
(
workbook
,
sheet
,
startRow
+
1
,
headers
,
dataList
);
// 写标
题
// 设置手动列宽,避免自动列宽计算导致的字体问
题
log
.
info
(
"开始插入标题------->"
+
System
.
currentTimeMillis
());
setupManualColumnWidth
(
sheet
,
headers
.
size
());
handlerExcelTitle
(
workbook
,
sheet
,
startRow
,
headers
);
}
//
写数据
//
最后写到 response
log
.
info
(
"
开始写入数据
------->"
+
System
.
currentTimeMillis
());
log
.
info
(
"
生成excel结束
------->"
+
System
.
currentTimeMillis
());
handlerExcelData
(
workbook
,
sheet
,
startRow
+
1
,
headers
,
dataList
);
writeExcelToResponse
(
"导出"
,
workbook
,
response
);
}
catch
(
Exception
e
)
{
log
.
error
(
"导出Excel异常"
,
e
);
sendError
(
response
,
"导出失败: "
+
e
.
getMessage
());
}
finally
{
// 确保资源释放
if
(
workbook
!=
null
)
{
try
{
workbook
.
close
();
}
catch
(
IOException
e
)
{
log
.
warn
(
"关闭workbook异常"
,
e
);
}
workbook
.
dispose
();
}
}
}
}
// 最后写到 response
/**
log
.
info
(
"生成excel结束------->"
+
System
.
currentTimeMillis
());
* 生成安全的sheet名称
writeExcelToResponse
(
"导出"
,
workbook
,
response
);
*/
private
String
generateSafeSheetName
(
String
name
)
{
if
(
StringUtils
.
isBlank
(
name
))
{
return
"Sheet1"
;
}
// 移除Excel不允许的字符:\ / * [ ] : ?
String
safeName
=
name
.
replaceAll
(
"[\\\\/*\\[\\]:?]"
,
""
);
// 限制长度在31个字符以内
return
safeName
.
length
()
>
31
?
safeName
.
substring
(
0
,
31
)
:
safeName
;
}
/**
* 设置手动列宽
*/
private
void
setupManualColumnWidth
(
Sheet
sheet
,
int
columnCount
)
{
try
{
// 根据列数设置合适的宽度,避免自动列宽计算
for
(
int
i
=
0
;
i
<
columnCount
;
i
++)
{
sheet
.
setColumnWidth
(
i
,
5000
);
// 设置固定宽度
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"设置列宽失败,使用默认宽度"
,
e
);
}
}
}
@SuppressWarnings
(
"unchecked"
)
@SuppressWarnings
(
"unchecked"
)
private
<
T
>
T
getParam
(
Map
<
String
,
Object
>
params
,
String
key
,
T
defaultVal
)
{
private
<
T
>
T
getParam
(
Map
<
String
,
Object
>
params
,
String
key
,
T
defaultVal
)
{
if
(
params
==
null
)
{
return
defaultVal
;
}
Object
val
=
params
.
get
(
key
);
Object
val
=
params
.
get
(
key
);
if
(
val
==
null
)
{
if
(
val
==
null
)
{
return
defaultVal
;
return
defaultVal
;
...
@@ -111,62 +176,77 @@ public class ExcelExportService {
...
@@ -111,62 +176,77 @@ public class ExcelExportService {
}
}
}
}
private
static
void
sendError
(
HttpServletResponse
response
,
String
msg
)
{
private
static
void
sendError
(
HttpServletResponse
response
,
String
msg
)
{
response
.
reset
();
try
{
response
.
setStatus
(
1
);
response
.
reset
();
response
.
setContentType
(
"text/plain;charset=UTF-8"
);
response
.
setStatus
(
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
);
try
{
response
.
setContentType
(
"text/plain;charset=UTF-8"
);
response
.
getWriter
().
write
(
msg
!=
null
?
msg
:
"未知错误"
);
response
.
getWriter
().
write
(
msg
!=
null
?
msg
:
"未知错误"
);
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
log
.
error
(
"发送错误响应失败"
,
e
);
}
}
}
}
// 插入图片(Base64)
// 插入图片(Base64)
private
void
drawImg
(
SXSSFWorkbook
workbook
,
Sheet
sheet
,
String
base64Img
,
int
rowIndex
,
int
endCol
)
{
private
void
drawImg
(
SXSSFWorkbook
workbook
,
Sheet
sheet
,
String
base64Img
,
int
rowIndex
,
int
endCol
)
{
Row
firstRow
=
sheet
.
createRow
(
rowIndex
);
try
{
for
(
int
i
=
0
;
i
<
endCol
;
i
++)
{
Row
firstRow
=
sheet
.
createRow
(
rowIndex
);
firstRow
.
createCell
(
i
);
for
(
int
i
=
0
;
i
<
endCol
;
i
++)
{
}
firstRow
.
createCell
(
i
);
firstRow
.
setHeightInPoints
(
230
);
// 合并单元格
sheet
.
addMergedRegion
(
new
CellRangeAddress
(
rowIndex
,
rowIndex
,
0
,
endCol
));
if
(
base64Img
!=
null
&&
!
base64Img
.
isBlank
())
{
int
start
=
base64Img
.
indexOf
(
","
);
if
(
start
!=
-
1
)
{
base64Img
=
base64Img
.
substring
(
start
+
1
);
}
}
byte
[]
imgBytes
=
Base64
.
getDecoder
().
decode
(
base64Img
);
firstRow
.
setHeightInPoints
(
230
);
// 合并单元格
sheet
.
addMergedRegion
(
new
CellRangeAddress
(
rowIndex
,
rowIndex
,
0
,
endCol
));
if
(
StringUtils
.
isNotBlank
(
base64Img
))
{
int
start
=
base64Img
.
indexOf
(
","
);
String
imgData
=
(
start
!=
-
1
)
?
base64Img
.
substring
(
start
+
1
)
:
base64Img
;
if
(
StringUtils
.
isNotBlank
(
imgData
))
{
byte
[]
imgBytes
=
Base64
.
getDecoder
().
decode
(
imgData
);
Drawing
<?>
drawingPatriarch
=
sheet
.
createDrawingPatriarch
();
Drawing
<?>
drawingPatriarch
=
sheet
.
createDrawingPatriarch
();
ClientAnchor
anchor
=
drawingPatriarch
.
createAnchor
(
0
,
0
,
0
,
0
,
0
,
rowIndex
,
endCol
,
rowIndex
+
1
);
ClientAnchor
anchor
=
drawingPatriarch
.
createAnchor
(
0
,
0
,
0
,
0
,
0
,
rowIndex
,
endCol
,
rowIndex
+
1
);
int
pictureIdx
=
workbook
.
addPicture
(
imgBytes
,
Workbook
.
PICTURE_TYPE_PNG
);
int
pictureIdx
=
workbook
.
addPicture
(
imgBytes
,
Workbook
.
PICTURE_TYPE_PNG
);
drawingPatriarch
.
createPicture
(
anchor
,
pictureIdx
);
drawingPatriarch
.
createPicture
(
anchor
,
pictureIdx
);
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"插入图片失败"
,
e
);
}
}
}
}
// 写入标题
// 写入标题
private
void
handlerExcelTitle
(
Workbook
workbook
,
Sheet
sheet
,
int
rowIndex
,
List
<
Header
>
headers
)
{
private
void
handlerExcelTitle
(
Workbook
workbook
,
Sheet
sheet
,
int
rowIndex
,
List
<
Header
>
headers
)
{
Row
row
=
sheet
.
createRow
(
rowIndex
);
if
(
headers
==
null
||
headers
.
isEmpty
())
{
CellStyle
titleStyle
=
getTitleStyle
(
workbook
);
return
;
if
(
headers
!=
null
&&
!
headers
.
isEmpty
())
{
}
try
{
Row
row
=
sheet
.
createRow
(
rowIndex
);
CellStyle
titleStyle
=
getTitleStyle
(
workbook
);
for
(
int
i
=
0
;
i
<
headers
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
headers
.
size
();
i
++)
{
Cell
cell
=
row
.
createCell
(
i
);
Cell
cell
=
row
.
createCell
(
i
);
cell
.
setCellStyle
(
titleStyle
);
cell
.
setCellStyle
(
titleStyle
);
cell
.
setCellValue
(
headers
.
get
(
i
).
getTitle
());
String
title
=
headers
.
get
(
i
)
!=
null
?
headers
.
get
(
i
).
getTitle
()
:
""
;
cell
.
setCellValue
(
title
!=
null
?
title
:
""
);
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"写入标题失败"
,
e
);
}
}
}
}
// Title 样式
// Title 样式
private
CellStyle
getTitleStyle
(
Workbook
workbook
)
{
private
CellStyle
getTitleStyle
(
Workbook
workbook
)
{
Font
font
=
workbook
.
createFont
();
font
.
setFontName
(
"Courier New"
);
font
.
setBold
(
true
);
CellStyle
style
=
workbook
.
createCellStyle
();
CellStyle
style
=
workbook
.
createCellStyle
();
style
.
setFont
(
font
);
try
{
Font
font
=
workbook
.
createFont
();
font
.
setFontName
(
"Arial"
);
// 使用通用字体
font
.
setBold
(
true
);
style
.
setFont
(
font
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"创建标题样式失败,使用默认样式"
,
e
);
}
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
return
style
;
...
@@ -175,54 +255,71 @@ private static void sendError(HttpServletResponse response, String msg) {
...
@@ -175,54 +255,71 @@ private static void sendError(HttpServletResponse response, String msg) {
// 写入数据
// 写入数据
private
void
handlerExcelData
(
Workbook
workbook
,
Sheet
sheet
,
int
startRowIndex
,
private
void
handlerExcelData
(
Workbook
workbook
,
Sheet
sheet
,
int
startRowIndex
,
List
<
Header
>
headers
,
List
<
Map
<
String
,
Object
>>
dataList
)
{
List
<
Header
>
headers
,
List
<
Map
<
String
,
Object
>>
dataList
)
{
CellStyle
dataStyle
=
getDataStyle
(
workbook
);
if
(
headers
==
null
||
headers
.
isEmpty
()
||
dataList
==
null
||
dataList
.
isEmpty
())
{
if
(
headers
!=
null
&&
!
headers
.
isEmpty
()
&&
dataList
!=
null
&&
!
dataList
.
isEmpty
())
{
return
;
}
try
{
CellStyle
dataStyle
=
getDataStyle
(
workbook
);
for
(
int
j
=
0
;
j
<
dataList
.
size
();
j
++)
{
for
(
int
j
=
0
;
j
<
dataList
.
size
();
j
++)
{
Row
row
=
sheet
.
createRow
(
startRowIndex
+
j
);
Row
row
=
sheet
.
createRow
(
startRowIndex
+
j
);
Map
<
String
,
Object
>
dataMap
=
dataList
.
get
(
j
);
Map
<
String
,
Object
>
dataMap
=
dataList
.
get
(
j
);
if
(
dataMap
==
null
)
{
continue
;
}
for
(
int
i
=
0
;
i
<
headers
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
headers
.
size
();
i
++)
{
Cell
cell
=
row
.
createCell
(
i
);
Cell
cell
=
row
.
createCell
(
i
);
cell
.
setCellStyle
(
dataStyle
);
cell
.
setCellStyle
(
dataStyle
);
Object
value
=
dataMap
.
get
(
headers
.
get
(
i
).
getField
());
Header
header
=
headers
.
get
(
i
);
if
(
header
==
null
)
{
cell
.
setCellValue
(
""
);
continue
;
}
String
field
=
header
.
getField
();
Object
value
=
dataMap
.
get
(
field
);
cell
.
setCellValue
(
value
==
null
?
""
:
value
.
toString
());
cell
.
setCellValue
(
value
==
null
?
""
:
value
.
toString
());
}
}
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"写入数据失败"
,
e
);
}
}
}
}
// 数据样式
// 数据样式
private
CellStyle
getDataStyle
(
Workbook
workbook
)
{
private
CellStyle
getDataStyle
(
Workbook
workbook
)
{
Font
font
=
workbook
.
createFont
();
font
.
setFontName
(
"Courier New"
);
CellStyle
style
=
workbook
.
createCellStyle
();
CellStyle
style
=
workbook
.
createCellStyle
();
style
.
setFont
(
font
);
try
{
Font
font
=
workbook
.
createFont
();
font
.
setFontName
(
"Arial"
);
// 使用通用字体
style
.
setFont
(
font
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"创建数据样式失败,使用默认样式"
,
e
);
}
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setAlignment
(
HorizontalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
style
.
setVerticalAlignment
(
VerticalAlignment
.
CENTER
);
return
style
;
return
style
;
}
}
// 将数据写入 HttpServletResponse
(流式,不占用内存)
// 将数据写入 HttpServletResponse
private
void
writeExcelToResponse
(
String
fileNamePrefix
,
SXSSFWorkbook
workbook
,
HttpServletResponse
response
)
{
private
void
writeExcelToResponse
(
String
fileNamePrefix
,
SXSSFWorkbook
workbook
,
HttpServletResponse
response
)
{
try
{
try
{
response
.
reset
();
response
.
reset
();
response
.
setContentType
(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
response
.
setContentType
(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
response
.
setHeader
(
"Content-disposition"
,
String
fileName
=
URLEncoder
.
encode
(
"attachment;filename="
+
URLEncoder
.
encode
(
fileNamePrefix
+
System
.
currentTimeMillis
()
+
".xlsx"
,
"UTF-8"
));
(
fileNamePrefix
!=
null
?
fileNamePrefix
:
"导出"
)
+
System
.
currentTimeMillis
()
+
".xlsx"
,
"UTF-8"
);
response
.
setHeader
(
"Content-Disposition"
,
"attachment;filename="
+
fileName
);
response
.
setHeader
(
"Access-Control-Expose-Headers"
,
"Content-Disposition"
);
response
.
setHeader
(
"Access-Control-Expose-Headers"
,
"Content-Disposition"
);
workbook
.
write
(
response
.
getOutputStream
());
workbook
.
write
(
response
.
getOutputStream
());
response
.
getOutputStream
().
flush
();
response
.
getOutputStream
().
flush
();
}
catch
(
IOException
e
)
{
}
catch
(
IOException
e
)
{
log
.
error
(
"写入响应流失败"
,
e
);
sendError
(
response
,
"导出 Excel 异常:"
+
e
.
getMessage
());
sendError
(
response
,
"导出 Excel 异常:"
+
e
.
getMessage
());
}
finally
{
// 清理临时文件
try
{
workbook
.
close
();
}
catch
(
IOException
ignored
)
{
}
workbook
.
dispose
();
}
}
}
}
}
}
\ No newline at end of file
src/main/resources/application.yml
浏览文件 @
f30ace05
server
:
server
:
port
:
8082
port
:
9120
spring
:
spring
:
application
:
application
:
name
:
excel-export-service
name
:
excel-export-service
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论