Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-oss
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xingmin
yd-oss
Commits
a294e8a3
Commit
a294e8a3
authored
Sep 23, 2025
by
zhangxingmin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
导出
parent
024c4054
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
1290 additions
and
1 deletions
+1290
-1
yd-oss-api/src/main/java/com/yd/oss/api/controller/ApiExcelController.java
+50
-0
yd-oss-api/src/main/java/com/yd/oss/api/service/ApiExcelService.java
+14
-0
yd-oss-api/src/main/java/com/yd/oss/api/service/impl/ApiExcelServiceImpl.java
+68
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelCollection.java
+15
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelField.java
+18
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelSheet.java
+14
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/client/ApiExcelFeignClient.java
+36
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/fallback/ApiExcelFeignFallbackFactory.java
+35
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/request/ApiOssExcelParseRequest.java
+13
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/request/ApiOssExportAppointmentExcelRequest.java
+26
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/response/ApiOssExcelParseResponse.java
+14
-0
yd-oss-feign/src/main/java/com/yd/oss/feign/response/ApiOssExportAppointmentExcelResponse.java
+12
-0
yd-oss-service/pom.xml
+28
-0
yd-oss-service/src/main/java/com/yd/oss/service/config/OssConfig.java
+10
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/AppointmentExcelService.java
+10
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/ExcelParserService.java
+9
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/ExcelService.java
+13
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/OssService.java
+4
-1
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/AliYunOssServiceImpl.java
+72
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/AppointmentExcelServiceImpl.java
+294
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/ExcelParserServiceImpl.java
+452
-0
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/ExcelServiceImpl.java
+83
-0
No files found.
yd-oss-api/src/main/java/com/yd/oss/api/controller/ApiExcelController.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
api
.
controller
;
import
com.yd.common.result.Result
;
import
com.yd.oss.api.service.ApiExcelService
;
import
com.yd.oss.feign.client.ApiExcelFeignClient
;
import
com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest
;
import
com.yd.oss.feign.response.ApiOssExcelParseResponse
;
import
com.yd.oss.feign.response.ApiOssExportAppointmentExcelResponse
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* Excel接口信息
*
* @author zxm
* @since 2025-07-31
*/
@RestController
@RequestMapping
(
"/excel"
)
@Validated
public
class
ApiExcelController
implements
ApiExcelFeignClient
{
@Autowired
private
ApiExcelService
apiExcelService
;
/**
* 导出excel-预约信息
* @param request
* @return
*/
@Override
public
Result
<
ApiOssExportAppointmentExcelResponse
>
exportAppointment
(
ApiOssExportAppointmentExcelRequest
request
)
{
return
apiExcelService
.
exportAppointment
(
request
);
}
/**
* 通用-Excel解析(支持多sheet页解析)
* @param file
* @param sheetClassNames
* @return
*/
@Override
public
Result
<
ApiOssExcelParseResponse
>
parse
(
MultipartFile
file
,
String
[]
sheetClassNames
)
{
return
apiExcelService
.
parse
(
file
,
sheetClassNames
);
}
}
yd-oss-api/src/main/java/com/yd/oss/api/service/ApiExcelService.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
api
.
service
;
import
com.yd.common.result.Result
;
import
com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest
;
import
com.yd.oss.feign.response.ApiOssExcelParseResponse
;
import
com.yd.oss.feign.response.ApiOssExportAppointmentExcelResponse
;
import
org.springframework.web.multipart.MultipartFile
;
public
interface
ApiExcelService
{
Result
<
ApiOssExportAppointmentExcelResponse
>
exportAppointment
(
ApiOssExportAppointmentExcelRequest
request
);
Result
<
ApiOssExcelParseResponse
>
parse
(
MultipartFile
file
,
String
[]
sheetClassNames
);
}
yd-oss-api/src/main/java/com/yd/oss/api/service/impl/ApiExcelServiceImpl.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
api
.
service
.
impl
;
import
com.yd.common.exception.BusinessException
;
import
com.yd.common.result.Result
;
import
com.yd.oss.api.service.ApiExcelService
;
import
com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest
;
import
com.yd.oss.feign.response.ApiOssExcelParseResponse
;
import
com.yd.oss.feign.response.ApiOssExportAppointmentExcelResponse
;
import
com.yd.oss.service.service.AppointmentExcelService
;
import
com.yd.oss.service.service.ExcelParserService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.util.Map
;
@Slf4j
@Service
public
class
ApiExcelServiceImpl
implements
ApiExcelService
{
@Autowired
private
AppointmentExcelService
appointmentExcelService
;
@Autowired
private
ExcelParserService
excelParserService
;
/**
* 导出excel-预约信息
* @param request
* @return
*/
@Override
public
Result
<
ApiOssExportAppointmentExcelResponse
>
exportAppointment
(
ApiOssExportAppointmentExcelRequest
request
)
{
ApiOssExportAppointmentExcelResponse
response
=
new
ApiOssExportAppointmentExcelResponse
();
String
url
=
appointmentExcelService
.
exportAppointment
(
request
.
getData
(),
request
.
getTemplateType
(),
request
.
getAppointmentBizId
());
response
.
setUrl
(
url
);
return
Result
.
success
(
response
);
}
/**
* 通用-Excel解析(支持多sheet页解析)
* @param file
* @param sheetClassNames
* @return
*/
@Override
public
Result
<
ApiOssExcelParseResponse
>
parse
(
MultipartFile
file
,
String
[]
sheetClassNames
)
{
ApiOssExcelParseResponse
response
=
new
ApiOssExcelParseResponse
();
try
{
// 将类名字符串转换为Class对象
Class
<?>[]
sheetClasses
=
new
Class
<?>[
sheetClassNames
.
length
];
for
(
int
i
=
0
;
i
<
sheetClassNames
.
length
;
i
++)
{
sheetClasses
[
i
]
=
Class
.
forName
(
sheetClassNames
[
i
]);
}
// 解析Excel
Map
<
Integer
,
Object
>
result
=
excelParserService
.
parseExcelWithMultipleSheets
(
file
,
sheetClasses
);
response
.
setMap
(
result
);
return
Result
.
success
(
response
);
}
catch
(
Exception
e
)
{
throw
new
BusinessException
(
"Excel解析异常!"
);
}
}
}
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelCollection.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
annotation
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
// Excel集合注解
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
FIELD
)
public
@interface
ExcelCollection
{
Class
<?>
type
();
// 集合元素类型
int
startRow
();
// 集合起始行
int
endRow
()
default
-
1
;
// 集合结束行(-1表示自动检测)
}
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelField.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
annotation
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
// Excel字段注解
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
FIELD
)
public
@interface
ExcelField
{
String
name
();
// 标题名称
int
titleRow
();
// 标题所在行
int
titleCol
();
// 标题所在列
int
valueRow
()
default
-
1
;
// 值所在行(默认与标题同行)
int
valueCol
()
default
-
1
;
// 值所在列(默认标题列+1)
String
dateFormat
()
default
"yyyy/MM/dd"
;
// 日期格式
}
yd-oss-feign/src/main/java/com/yd/oss/feign/annotation/ExcelSheet.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
annotation
;
import
java.lang.annotation.ElementType
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.lang.annotation.Target
;
// Excel Sheet注解
@Retention
(
RetentionPolicy
.
RUNTIME
)
@Target
(
ElementType
.
TYPE
)
public
@interface
ExcelSheet
{
int
sheetIndex
()
default
0
;
// Sheet索引
String
sheetName
()
default
""
;
// Sheet名称
}
yd-oss-feign/src/main/java/com/yd/oss/feign/client/ApiExcelFeignClient.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
client
;
import
com.yd.common.result.Result
;
import
com.yd.oss.feign.fallback.ApiExcelFeignFallbackFactory
;
import
com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest
;
import
com.yd.oss.feign.response.ApiOssExcelParseResponse
;
import
com.yd.oss.feign.response.ApiOssExportAppointmentExcelResponse
;
import
org.springframework.cloud.openfeign.FeignClient
;
import
org.springframework.http.MediaType
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestPart
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* Excel信息Feign客户端
*/
@FeignClient
(
name
=
"yd-oss-api"
,
fallbackFactory
=
ApiExcelFeignFallbackFactory
.
class
)
public
interface
ApiExcelFeignClient
{
/**
* 导出excel-预约信息
* @return
*/
@PostMapping
(
"/export/appointment"
)
Result
<
ApiOssExportAppointmentExcelResponse
>
exportAppointment
(
@Validated
@RequestBody
ApiOssExportAppointmentExcelRequest
request
);
/**
* 通用-Excel解析(支持多sheet页解析)
* @return
*/
@PostMapping
(
value
=
"/parse-excel"
,
consumes
=
MediaType
.
MULTIPART_FORM_DATA_VALUE
)
Result
<
ApiOssExcelParseResponse
>
parse
(
@RequestPart
(
"file"
)
MultipartFile
file
,
@RequestPart
(
"sheetClassNames"
)
String
[]
sheetClassNames
);
}
yd-oss-feign/src/main/java/com/yd/oss/feign/fallback/ApiExcelFeignFallbackFactory.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
fallback
;
import
com.yd.common.result.Result
;
import
com.yd.oss.feign.client.ApiExcelFeignClient
;
import
com.yd.oss.feign.request.ApiOssExcelParseRequest
;
import
com.yd.oss.feign.request.ApiOssExportAppointmentExcelRequest
;
import
com.yd.oss.feign.response.ApiOssExcelParseResponse
;
import
com.yd.oss.feign.response.ApiOssExportAppointmentExcelResponse
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.cloud.openfeign.FallbackFactory
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* Excel信息Feign降级处理
*/
@Slf4j
@Component
public
class
ApiExcelFeignFallbackFactory
implements
FallbackFactory
<
ApiExcelFeignClient
>
{
@Override
public
ApiExcelFeignClient
create
(
Throwable
cause
)
{
return
new
ApiExcelFeignClient
()
{
@Override
public
Result
<
ApiOssExportAppointmentExcelResponse
>
exportAppointment
(
ApiOssExportAppointmentExcelRequest
request
)
{
return
null
;
}
@Override
public
Result
<
ApiOssExcelParseResponse
>
parse
(
MultipartFile
file
,
String
[]
sheetClassNames
)
{
return
null
;
}
};
}
}
yd-oss-feign/src/main/java/com/yd/oss/feign/request/ApiOssExcelParseRequest.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
request
;
import
lombok.Data
;
@Data
public
class
ApiOssExcelParseRequest
{
/**
* Sheet对应类的全限定名
*/
private
String
[]
sheetClassNames
;
}
yd-oss-feign/src/main/java/com/yd/oss/feign/request/ApiOssExportAppointmentExcelRequest.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
request
;
import
lombok.Data
;
import
javax.validation.constraints.NotBlank
;
import
java.util.Map
;
@Data
public
class
ApiOssExportAppointmentExcelRequest
{
/**
* 需要导出的数据
*/
private
Map
<
String
,
Object
>
data
;
/**
* 生成文件的模板类型
*/
@NotBlank
(
message
=
"生成文件的模板类型不能为空"
)
private
String
templateType
;
/**
* 预约信息主表唯一业务ID
*/
private
String
appointmentBizId
;
}
yd-oss-feign/src/main/java/com/yd/oss/feign/response/ApiOssExcelParseResponse.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
response
;
import
lombok.Data
;
import
java.util.Map
;
@Data
public
class
ApiOssExcelParseResponse
{
/**
* 返回解析后的数据
*/
private
Map
<
Integer
,
Object
>
map
;
}
yd-oss-feign/src/main/java/com/yd/oss/feign/response/ApiOssExportAppointmentExcelResponse.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
feign
.
response
;
import
lombok.Data
;
@Data
public
class
ApiOssExportAppointmentExcelResponse
{
/**
* 访问路径
*/
private
String
url
;
}
yd-oss-service/pom.xml
View file @
a294e8a3
...
...
@@ -95,5 +95,33 @@
<groupId>
org.apache.xmlbeans
</groupId>
<artifactId>
xmlbeans
</artifactId>
</dependency>
<!-- EasyPOI核心依赖 -->
<dependency>
<groupId>
cn.afterturn
</groupId>
<artifactId>
easypoi-base
</artifactId>
</dependency>
<dependency>
<groupId>
cn.afterturn
</groupId>
<artifactId>
easypoi-web
</artifactId>
</dependency>
<dependency>
<groupId>
cn.afterturn
</groupId>
<artifactId>
easypoi-annotation
</artifactId>
</dependency>
<!-- EasyPOI模板功能需要的额外依赖 -->
<dependency>
<groupId>
org.freemarker
</groupId>
<artifactId>
freemarker
</artifactId>
</dependency>
<dependency>
<groupId>
org.jxls
</groupId>
<artifactId>
jxls
</artifactId>
</dependency>
<dependency>
<groupId>
org.jxls
</groupId>
<artifactId>
jxls-poi
</artifactId>
</dependency>
</dependencies>
</project>
yd-oss-service/src/main/java/com/yd/oss/service/config/OssConfig.java
View file @
a294e8a3
...
...
@@ -18,14 +18,19 @@ public class OssConfig {
@Autowired
private
IOssProviderService
ossProviderService
;
//默认的OSS提供商
private
OssProvider
currentProvider
;
//默认存储桶名称
private
String
defaultBucket
;
//默认服务端点
private
String
defaultEndpoint
;
@PostConstruct
public
void
init
()
{
try
{
this
.
currentProvider
=
ossProviderService
.
getDefaultProvider
();
this
.
defaultBucket
=
currentProvider
.
getBucketName
();
this
.
defaultEndpoint
=
currentProvider
.
getEndpoint
();
log
.
info
(
"默认OSS提供商初始化成功: {}"
,
currentProvider
.
getName
());
}
catch
(
Exception
e
)
{
log
.
error
(
"默认OSS提供商初始化失败"
,
e
);
...
...
@@ -49,6 +54,11 @@ public class OssConfig {
}
@Bean
public
String
defaultEndpoint
()
{
return
defaultEndpoint
;
}
@Bean
public
OssProvider
currentProvider
()
{
return
currentProvider
;
}
...
...
yd-oss-service/src/main/java/com/yd/oss/service/service/AppointmentExcelService.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
;
import
java.util.Map
;
public
interface
AppointmentExcelService
{
String
exportAppointment
(
Map
<
String
,
Object
>
data
,
String
templateType
,
String
appointmentBizId
);
}
yd-oss-service/src/main/java/com/yd/oss/service/service/ExcelParserService.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.util.Map
;
public
interface
ExcelParserService
{
Map
<
Integer
,
Object
>
parseExcelWithMultipleSheets
(
MultipartFile
file
,
Class
<?>...
sheetClasses
)
throws
Exception
;
}
yd-oss-service/src/main/java/com/yd/oss/service/service/ExcelService.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
;
import
org.apache.poi.ss.usermodel.Workbook
;
import
java.io.File
;
import
java.io.IOException
;
public
interface
ExcelService
{
File
downloadTemplateToTempFile
(
String
ossObjectKey
)
throws
IOException
;
File
saveWorkbookToTempFile
(
Workbook
workbook
)
throws
IOException
;
}
yd-oss-service/src/main/java/com/yd/oss/service/service/OssService.java
View file @
a294e8a3
...
...
@@ -3,7 +3,6 @@ package com.yd.oss.service.service;
import
com.yd.oss.service.dto.FileMetadata
;
import
com.yd.oss.service.dto.UploadResult
;
import
com.yd.oss.service.model.OssProvider
;
import
java.io.InputStream
;
import
java.time.Duration
;
...
...
@@ -65,5 +64,9 @@ public interface OssService {
// 获取上传结果(包含文件信息和访问URL)
UploadResult
getUploadResult
(
String
fileKey
,
Duration
expiration
);
String
upload
(
byte
[]
content
,
String
fileName
);
String
upload
(
InputStream
inputStream
,
String
fileName
);
}
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/AliYunOssServiceImpl.java
View file @
a294e8a3
...
...
@@ -2,6 +2,7 @@ package com.yd.oss.service.service.impl;
import
com.aliyun.oss.OSS
;
import
com.aliyun.oss.OSSClientBuilder
;
import
com.aliyun.oss.model.CannedAccessControlList
;
import
com.aliyun.oss.model.OSSObject
;
import
com.aliyun.oss.model.ObjectMetadata
;
import
com.aliyun.oss.model.PutObjectRequest
;
...
...
@@ -49,6 +50,9 @@ public class AliYunOssServiceImpl implements OssService {
private
String
defaultBucket
;
// 注入默认存储桶
@Autowired
private
String
defaultEndpoint
;
// 注入默认服务端点
@Autowired
private
OssProvider
currentProvider
;
// 注入当前提供商
/**
...
...
@@ -714,4 +718,72 @@ public class AliYunOssServiceImpl implements OssService {
file
.
setId
(-
1L
);
// 使用无效ID
return
file
;
}
/**
* 上传字节数组到OSS并设置为公共读权限
* @param content 文件内容字节数组
* @param fileName 文件名
* @return 公共访问URL
*/
public
String
upload
(
byte
[]
content
,
String
fileName
)
{
try
{
// 生成唯一文件名
String
objectName
=
"appointment/excel/"
+
UUID
.
randomUUID
()
+
"/"
+
fileName
;
// 创建上传请求
PutObjectRequest
putObjectRequest
=
new
PutObjectRequest
(
defaultBucket
,
objectName
,
new
ByteArrayInputStream
(
content
)
);
// 上传文件
ossClient
.
putObject
(
putObjectRequest
);
// 设置对象访问权限为公共读
ossClient
.
setObjectAcl
(
defaultBucket
,
objectName
,
CannedAccessControlList
.
PublicRead
);
// 构造公共访问URL
String
publicUrl
=
"https://"
+
defaultBucket
+
"."
+
defaultEndpoint
+
"/"
+
objectName
;
return
publicUrl
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"上传文件到OSS失败"
,
e
);
}
}
/**
* 上传输入流到OSS并设置为公共读权限
* @param inputStream 文件输入流
* @param fileName 文件名
* @return 公共访问URL
*/
public
String
upload
(
InputStream
inputStream
,
String
fileName
)
{
try
{
// 生成唯一文件名
String
objectName
=
"appointment/excel/"
+
UUID
.
randomUUID
()
+
"/"
+
fileName
;
// 创建上传请求
PutObjectRequest
putObjectRequest
=
new
PutObjectRequest
(
defaultBucket
,
objectName
,
inputStream
);
// 上传文件
ossClient
.
putObject
(
putObjectRequest
);
// 设置对象访问权限为公共读
ossClient
.
setObjectAcl
(
defaultBucket
,
objectName
,
CannedAccessControlList
.
PublicRead
);
// 构造公共访问URL
String
publicUrl
=
"https://"
+
defaultBucket
+
"."
+
defaultEndpoint
+
"/"
+
objectName
;
return
publicUrl
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"上传文件到OSS失败"
,
e
);
}
}
}
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/AppointmentExcelServiceImpl.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
.
impl
;
import
cn.afterturn.easypoi.excel.ExcelExportUtil
;
import
cn.afterturn.easypoi.excel.entity.TemplateExportParams
;
import
com.yd.common.utils.CommonUtil
;
import
com.yd.common.utils.DateUtil
;
import
com.yd.oss.service.dto.FileProdDto
;
import
com.yd.oss.service.service.AppointmentExcelService
;
import
com.yd.oss.service.service.ExcelService
;
import
com.yd.oss.service.service.IFileTemplateService
;
import
com.yd.oss.service.service.OssService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.poi.ss.usermodel.*
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.io.*
;
import
java.util.*
;
/**
* 预约Excel服务类
*/
@Slf4j
@Service
public
class
AppointmentExcelServiceImpl
implements
AppointmentExcelService
{
@Resource
private
OssService
ossService
;
@Resource
private
ExcelService
excelService
;
@Resource
private
IFileTemplateService
iFileTemplateService
;
/**
* 导出预约信息并上传到OSS
* @param data
* @param ossObjectKey
* @param fileName
* @return
*/
public
String
exportAndUploadToOss
(
Map
<
String
,
Object
>
data
,
String
ossObjectKey
,
String
fileName
)
{
File
tempFile
=
null
;
File
processedFile
=
null
;
try
{
// 1. 从OSS下载模板到临时文件
tempFile
=
excelService
.
downloadTemplateToTempFile
(
ossObjectKey
);
// 2. 使用临时文件路径创建TemplateExportParams
TemplateExportParams
params
=
new
TemplateExportParams
(
tempFile
.
getAbsolutePath
());
params
.
setSheetName
(
new
String
[]{
"預約信息及個人資料"
,
"健康信息及聲明"
,
"財務資料分析(FNA)"
});
params
.
setScanAllsheet
(
true
);
// 3. 导出Excel
Workbook
workbook
=
ExcelExportUtil
.
exportExcel
(
params
,
data
);
// 4. 处理受益人动态行
processedFile
=
processBeneficiaryRows
(
workbook
,
data
);
// 5. 重新读取处理后的工作簿
workbook
.
close
();
workbook
=
WorkbookFactory
.
create
(
processedFile
);
// 6. 将Workbook转换为字节数组
ByteArrayOutputStream
outputStream
=
new
ByteArrayOutputStream
();
workbook
.
write
(
outputStream
);
byte
[]
excelBytes
=
outputStream
.
toByteArray
();
// 7. 上传到OSS
String
ossUrl
=
ossService
.
upload
(
excelBytes
,
fileName
);
// 8. 关闭资源
outputStream
.
close
();
workbook
.
close
();
return
ossUrl
;
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
"从OSS模板导出失败"
,
e
);
}
finally
{
// 9. 删除临时文件
if
(
tempFile
!=
null
&&
tempFile
.
exists
())
{
tempFile
.
delete
();
}
if
(
processedFile
!=
null
&&
processedFile
.
exists
())
{
processedFile
.
delete
();
}
}
}
/**
* 处理受益人动态行
* @param workbook
* @param data
* @return
* @throws IOException
*/
public
File
processBeneficiaryRows
(
Workbook
workbook
,
Map
<
String
,
Object
>
data
)
throws
IOException
{
Sheet
sheet
=
workbook
.
getSheet
(
"預約信息及個人資料"
);
// 查找受益人数据区域的起始行和结束行
int
beneficiaryTitleRowNum
=
-
1
;
int
secondHolderRowNum
=
-
1
;
for
(
Row
row
:
sheet
)
{
for
(
Cell
cell
:
row
)
{
if
(
cell
.
getCellType
()
==
CellType
.
STRING
)
{
String
value
=
cell
.
getStringCellValue
();
if
(
"受益人资料"
.
equals
(
value
.
trim
()))
{
beneficiaryTitleRowNum
=
row
.
getRowNum
();
}
else
if
(
"第二持有人资料(只适用儿童单)"
.
equals
(
value
.
trim
()))
{
secondHolderRowNum
=
row
.
getRowNum
();
break
;
}
}
}
if
(
beneficiaryTitleRowNum
!=
-
1
&&
secondHolderRowNum
!=
-
1
)
{
break
;
}
}
if
(
beneficiaryTitleRowNum
==
-
1
||
secondHolderRowNum
==
-
1
)
{
throw
new
RuntimeException
(
"未找到受益人资料或第二持有人资料区域"
);
}
// 获取第二持有人资料中英文姓名的列宽作为参考
int
englishNameColumnWidth
=
-
1
;
Row
secondHolderRow
=
sheet
.
getRow
(
secondHolderRowNum
+
2
);
// 假设第二持有人资料在标题行后两行
if
(
secondHolderRow
!=
null
)
{
Cell
englishNameCell
=
secondHolderRow
.
getCell
(
3
);
// 英文姓名通常在D列(索引3)
if
(
englishNameCell
!=
null
)
{
englishNameColumnWidth
=
sheet
.
getColumnWidth
(
3
);
// 获取D列的宽度
}
}
// 获取受益人列表
List
<
Map
<
String
,
Object
>>
beneficiaries
=
(
List
<
Map
<
String
,
Object
>>)
data
.
get
(
"beneficiaryList"
);
if
(
beneficiaries
==
null
||
beneficiaries
.
isEmpty
())
{
return
excelService
.
saveWorkbookToTempFile
(
workbook
);
}
// 创建样式 - 保持与模板一致的边框
CellStyle
dataStyle
=
workbook
.
createCellStyle
();
dataStyle
.
setBorderTop
(
BorderStyle
.
THIN
);
dataStyle
.
setBorderBottom
(
BorderStyle
.
THIN
);
dataStyle
.
setBorderLeft
(
BorderStyle
.
THIN
);
dataStyle
.
setBorderRight
(
BorderStyle
.
THIN
);
// 创建浅黄色背景样式 (#F4F492)
CellStyle
yellowBgStyle
=
workbook
.
createCellStyle
();
yellowBgStyle
.
cloneStyleFrom
(
dataStyle
);
yellowBgStyle
.
setFillPattern
(
FillPatternType
.
SOLID_FOREGROUND
);
yellowBgStyle
.
setFillForegroundColor
(
IndexedColors
.
LIGHT_YELLOW
.
getIndex
());
// 创建浅橙色背景样式 (#F8CBAD)
CellStyle
orangeBgStyle
=
workbook
.
createCellStyle
();
orangeBgStyle
.
cloneStyleFrom
(
dataStyle
);
orangeBgStyle
.
setFillPattern
(
FillPatternType
.
SOLID_FOREGROUND
);
orangeBgStyle
.
setFillForegroundColor
(
IndexedColors
.
LIGHT_ORANGE
.
getIndex
());
// 计算需要插入的行数
int
existingBeneficiaryRows
=
secondHolderRowNum
-
beneficiaryTitleRowNum
-
1
;
// 减去标题行
int
neededBeneficiaryRows
=
beneficiaries
.
size
()
*
4
+
(
beneficiaries
.
size
()
-
1
);
// 每个受益人占4行,加上空行
int
rowsToInsert
=
neededBeneficiaryRows
-
existingBeneficiaryRows
;
if
(
rowsToInsert
>
0
)
{
// 下移后续内容,包括第二持有人资料
sheet
.
shiftRows
(
secondHolderRowNum
,
sheet
.
getLastRowNum
(),
rowsToInsert
,
true
,
false
);
secondHolderRowNum
+=
rowsToInsert
;
// 更新第二持有人资料的行号
}
else
if
(
rowsToInsert
<
0
)
{
// 上移后续内容
sheet
.
shiftRows
(
secondHolderRowNum
,
sheet
.
getLastRowNum
(),
rowsToInsert
,
true
,
false
);
secondHolderRowNum
+=
rowsToInsert
;
// 更新第二持有人资料的行号
}
// 清除模板中的示例数据(保留"受益人资料"标题行)
for
(
int
i
=
beneficiaryTitleRowNum
+
1
;
i
<
secondHolderRowNum
;
i
++)
{
Row
row
=
sheet
.
getRow
(
i
);
if
(
row
!=
null
)
{
sheet
.
removeRow
(
row
);
}
}
// 插入新的受益人数据
int
currentRow
=
beneficiaryTitleRowNum
+
1
;
// 从标题行下一行开始(空行)
for
(
int
i
=
0
;
i
<
beneficiaries
.
size
();
i
++)
{
Map
<
String
,
Object
>
beneficiary
=
beneficiaries
.
get
(
i
);
// 1. 添加受益人标题行
Row
titleRow
=
sheet
.
createRow
(
currentRow
++);
Cell
titleCell
=
titleRow
.
createCell
(
0
);
titleCell
.
setCellValue
(
"受益人"
+
(
i
+
1
));
titleCell
.
setCellStyle
(
dataStyle
);
// 2. 添加姓名行
Row
nameRow
=
sheet
.
createRow
(
currentRow
++);
// 中文姓名
Cell
nameLabelCell
=
nameRow
.
createCell
(
0
);
nameLabelCell
.
setCellValue
(
"中文姓名"
);
nameLabelCell
.
setCellStyle
(
dataStyle
);
Cell
nameValueCell
=
nameRow
.
createCell
(
1
);
nameValueCell
.
setCellValue
((
String
)
beneficiary
.
get
(
"beneficiaryName"
));
nameValueCell
.
setCellStyle
(
yellowBgStyle
);
// 应用浅黄色背景
// 英文姓名
Cell
enNameLabelCell
=
nameRow
.
createCell
(
2
);
enNameLabelCell
.
setCellValue
(
"英文姓名 (同护照)"
);
enNameLabelCell
.
setCellStyle
(
dataStyle
);
Cell
enNameValueCell
=
nameRow
.
createCell
(
3
);
enNameValueCell
.
setCellValue
((
String
)
beneficiary
.
get
(
"beneficiaryNameEn"
));
enNameValueCell
.
setCellStyle
(
yellowBgStyle
);
// 应用浅黄色背景
// 设置英文姓名列宽与第二持有人资料一致
if
(
englishNameColumnWidth
!=
-
1
)
{
sheet
.
setColumnWidth
(
3
,
englishNameColumnWidth
);
}
// 3. 添加生日/ID行
Row
infoRow
=
sheet
.
createRow
(
currentRow
++);
// 出生日期
Cell
birthLabelCell
=
infoRow
.
createCell
(
0
);
birthLabelCell
.
setCellValue
(
"出生日期 (西元 年/月/日)"
);
birthLabelCell
.
setCellStyle
(
dataStyle
);
Cell
birthValueCell
=
infoRow
.
createCell
(
1
);
birthValueCell
.
setCellValue
(
DateUtil
.
formatDate
(
beneficiary
.
get
(
"beneficiaryBirthTime"
)));
birthValueCell
.
setCellStyle
(
yellowBgStyle
);
// 应用浅黄色背景
// 身份证号码
Cell
idLabelCell
=
infoRow
.
createCell
(
2
);
idLabelCell
.
setCellValue
(
"身份证号码"
);
idLabelCell
.
setCellStyle
(
dataStyle
);
Cell
idValueCell
=
infoRow
.
createCell
(
3
);
idValueCell
.
setCellValue
((
String
)
beneficiary
.
get
(
"beneficiaryIdNumber"
));
idValueCell
.
setCellStyle
(
yellowBgStyle
);
// 应用浅黄色背景
// 4. 添加关系/比例行
Row
relationRow
=
sheet
.
createRow
(
currentRow
++);
// 與受保人關係
Cell
relationLabelCell
=
relationRow
.
createCell
(
0
);
relationLabelCell
.
setCellValue
(
"與受保人關係"
);
relationLabelCell
.
setCellStyle
(
dataStyle
);
Cell
relationValueCell
=
relationRow
.
createCell
(
1
);
relationValueCell
.
setCellValue
((
String
)
beneficiary
.
get
(
"beneficiaryInsurantRel"
));
relationValueCell
.
setCellStyle
(
orangeBgStyle
);
// 应用浅橙色背景
// 受益比例
Cell
percentLabelCell
=
relationRow
.
createCell
(
2
);
percentLabelCell
.
setCellValue
(
"受益比例 (%)"
);
percentLabelCell
.
setCellStyle
(
dataStyle
);
Cell
percentValueCell
=
relationRow
.
createCell
(
3
);
percentValueCell
.
setCellValue
(
CommonUtil
.
formatPercentage
(
beneficiary
.
get
(
"beneficiaryBenefitRatio"
)));
percentValueCell
.
setCellStyle
(
yellowBgStyle
);
// 应用浅黄色背景
// 5. 在受益人之间添加空行(除了最后一个受益人)
if
(
i
<
beneficiaries
.
size
()
-
1
)
{
currentRow
++;
// 添加空行
}
}
// 确保第二持有人资料上方有一行空行
secondHolderRow
=
sheet
.
getRow
(
secondHolderRowNum
);
if
(
secondHolderRow
!=
null
)
{
sheet
.
shiftRows
(
secondHolderRowNum
,
sheet
.
getLastRowNum
(),
1
,
true
,
false
);
// 创建一个空行
Row
emptyRow
=
sheet
.
createRow
(
secondHolderRowNum
);
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
Cell
cell
=
emptyRow
.
createCell
(
i
);
cell
.
setCellValue
(
""
);
}
}
return
excelService
.
saveWorkbookToTempFile
(
workbook
);
}
/**
* 导出excel-预约信息
* @param data
* @param templateType
* @param appointmentBizId
* @return
*/
@Override
public
String
exportAppointment
(
Map
<
String
,
Object
>
data
,
String
templateType
,
String
appointmentBizId
)
{
// 获取模板信息
FileProdDto
fileProdDto
=
iFileTemplateService
.
getFileProd
(
""
,
templateType
);
// 生成文件名
String
fileName
=
"预约信息_"
+
appointmentBizId
+
"_"
+
System
.
currentTimeMillis
()
+
".xlsx"
;
// 导出并上传到OSS
return
exportAndUploadToOss
(
data
,
fileProdDto
.
getFileKey
(),
fileName
);
}
}
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/ExcelParserServiceImpl.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
.
impl
;
import
com.yd.oss.feign.annotation.ExcelCollection
;
import
com.yd.oss.feign.annotation.ExcelField
;
import
com.yd.oss.feign.annotation.ExcelSheet
;
import
com.yd.oss.service.service.ExcelParserService
;
import
org.apache.poi.ss.usermodel.*
;
import
org.apache.poi.ss.util.CellRangeAddress
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Service
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.lang.reflect.Field
;
import
java.math.BigDecimal
;
import
java.text.ParseException
;
import
java.text.SimpleDateFormat
;
import
java.util.*
;
/**
* Excel通用解析器
*/
@Service
public
class
ExcelParserServiceImpl
implements
ExcelParserService
{
private
static
final
Logger
log
=
LoggerFactory
.
getLogger
(
ExcelParserServiceImpl
.
class
);
/**
* 解析Excel文件(多Sheet页版本)
* @param file 上传的Excel文件
* @param sheetClasses 每个Sheet页对应的类类型
* @return 包含所有Sheet页解析结果的Map,key为Sheet索引
*/
@Override
public
Map
<
Integer
,
Object
>
parseExcelWithMultipleSheets
(
MultipartFile
file
,
Class
<?>...
sheetClasses
)
throws
Exception
{
Map
<
Integer
,
Object
>
resultMap
=
new
HashMap
<>();
Workbook
workbook
=
WorkbookFactory
.
create
(
file
.
getInputStream
());
try
{
for
(
Class
<?>
clazz
:
sheetClasses
)
{
ExcelSheet
sheetAnnotation
=
clazz
.
getAnnotation
(
ExcelSheet
.
class
);
if
(
sheetAnnotation
==
null
)
{
log
.
warn
(
"类 {} 缺少@ExcelSheet注解,跳过处理"
,
clazz
.
getSimpleName
());
continue
;
}
Sheet
sheet
;
if
(!
sheetAnnotation
.
sheetName
().
isEmpty
())
{
sheet
=
workbook
.
getSheet
(
sheetAnnotation
.
sheetName
());
}
else
{
sheet
=
workbook
.
getSheetAt
(
sheetAnnotation
.
sheetIndex
());
}
if
(
sheet
==
null
)
{
log
.
warn
(
"Sheet {} 不存在"
,
!
sheetAnnotation
.
sheetName
().
isEmpty
()
?
sheetAnnotation
.
sheetName
()
:
sheetAnnotation
.
sheetIndex
());
continue
;
}
Object
instance
=
clazz
.
getDeclaredConstructor
().
newInstance
();
// 解析普通字段
parseFieldsWithMergedCells
(
sheet
,
instance
);
// 解析集合字段
parseCollectionFields
(
sheet
,
instance
);
resultMap
.
put
(
sheetAnnotation
.
sheetIndex
(),
instance
);
}
return
resultMap
;
}
finally
{
workbook
.
close
();
}
}
/**
* 解析单个Sheet页
* @param file
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
public
<
T
>
T
parseExcel
(
MultipartFile
file
,
Class
<
T
>
clazz
)
throws
Exception
{
Workbook
workbook
=
WorkbookFactory
.
create
(
file
.
getInputStream
());
try
{
// 获取Sheet信息
ExcelSheet
sheetAnnotation
=
clazz
.
getAnnotation
(
ExcelSheet
.
class
);
Sheet
sheet
;
if
(
sheetAnnotation
!=
null
)
{
if
(!
sheetAnnotation
.
sheetName
().
isEmpty
())
{
sheet
=
workbook
.
getSheet
(
sheetAnnotation
.
sheetName
());
}
else
{
sheet
=
workbook
.
getSheetAt
(
sheetAnnotation
.
sheetIndex
());
}
}
else
{
sheet
=
workbook
.
getSheetAt
(
0
);
// 默认第一个Sheet
}
if
(
sheet
==
null
)
{
throw
new
RuntimeException
(
"Sheet not found"
);
}
T
instance
=
clazz
.
getDeclaredConstructor
().
newInstance
();
// 解析普通字段
parseFieldsWithMergedCells
(
sheet
,
instance
);
// 解析集合字段
parseCollectionFields
(
sheet
,
instance
);
return
instance
;
}
finally
{
workbook
.
close
();
}
}
/**
* 处理合并单元格的字段解析
* @param sheet
* @param instance
*/
private
void
parseFieldsWithMergedCells
(
Sheet
sheet
,
Object
instance
)
{
// 获取所有合并区域
List
<
CellRangeAddress
>
mergedRegions
=
sheet
.
getMergedRegions
();
for
(
Field
field
:
instance
.
getClass
().
getDeclaredFields
())
{
ExcelField
excelField
=
field
.
getAnnotation
(
ExcelField
.
class
);
if
(
excelField
!=
null
)
{
try
{
int
valueRow
=
excelField
.
valueRow
()
>=
0
?
excelField
.
valueRow
()
:
excelField
.
titleRow
();
int
valueCol
=
excelField
.
valueCol
()
>=
0
?
excelField
.
valueCol
()
:
excelField
.
titleCol
()
+
1
;
Row
row
=
sheet
.
getRow
(
valueRow
);
if
(
row
==
null
)
continue
;
// 检查是否为合并单元格
CellRangeAddress
mergedRegion
=
findMergedRegion
(
mergedRegions
,
valueRow
,
valueCol
);
String
cellValue
;
if
(
mergedRegion
!=
null
)
{
// 合并单元格,取第一个单元格的值
Cell
firstCell
=
sheet
.
getRow
(
mergedRegion
.
getFirstRow
())
.
getCell
(
mergedRegion
.
getFirstColumn
());
cellValue
=
getCellValueAsString
(
firstCell
);
}
else
{
// 普通单元格
Cell
cell
=
row
.
getCell
(
valueCol
);
cellValue
=
cell
!=
null
?
getCellValueAsString
(
cell
)
:
null
;
}
if
(
cellValue
==
null
||
cellValue
.
trim
().
isEmpty
())
{
continue
;
}
// 转换单元格值
Object
value
=
convertStringValue
(
cellValue
,
field
.
getType
(),
excelField
.
dateFormat
());
// 设置字段值
field
.
setAccessible
(
true
);
field
.
set
(
instance
,
value
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"设置字段 {} 值失败: {}"
,
field
.
getName
(),
e
.
getMessage
());
}
}
}
}
/**
* 查找包含指定行列的合并区域
* @param mergedRegions
* @param row
* @param column
* @return
*/
private
CellRangeAddress
findMergedRegion
(
List
<
CellRangeAddress
>
mergedRegions
,
int
row
,
int
column
)
{
for
(
CellRangeAddress
region
:
mergedRegions
)
{
if
(
region
.
isInRange
(
row
,
column
))
{
return
region
;
}
}
return
null
;
}
/**
* 将单元格值转换为字符串
* @param cell 单元格对象
* @return 单元格值的字符串表示,若单元格为null则返回null
*/
private
String
getCellValueAsString
(
Cell
cell
)
{
if
(
cell
==
null
)
{
return
null
;
}
switch
(
cell
.
getCellType
())
{
case
STRING:
return
cell
.
getStringCellValue
().
trim
();
case
NUMERIC:
if
(
DateUtil
.
isCellDateFormatted
(
cell
))
{
// 日期类型转换为字符串(可根据需要调整格式)
return
new
SimpleDateFormat
(
"yyyy-MM-dd HH:mm:ss"
).
format
(
cell
.
getDateCellValue
());
}
else
{
// 数字类型避免科学计数法,转换为字符串
return
String
.
valueOf
(
cell
.
getNumericCellValue
());
}
case
BOOLEAN:
return
String
.
valueOf
(
cell
.
getBooleanCellValue
());
case
FORMULA:
try
{
return
cell
.
getStringCellValue
().
trim
();
}
catch
(
Exception
e
)
{
// 公式计算结果不是字符串时,尝试获取数值
return
String
.
valueOf
(
cell
.
getNumericCellValue
());
}
default
:
return
""
;
}
}
/**
* 解析普通字段
* @param sheet
* @param instance
*/
private
void
parseFields
(
Sheet
sheet
,
Object
instance
)
{
for
(
Field
field
:
instance
.
getClass
().
getDeclaredFields
())
{
ExcelField
excelField
=
field
.
getAnnotation
(
ExcelField
.
class
);
if
(
excelField
!=
null
)
{
try
{
// 获取值单元格
int
valueRow
=
excelField
.
valueRow
()
>=
0
?
excelField
.
valueRow
()
:
excelField
.
titleRow
();
int
valueCol
=
excelField
.
valueCol
()
>=
0
?
excelField
.
valueCol
()
:
excelField
.
titleCol
()
+
1
;
Row
row
=
sheet
.
getRow
(
valueRow
);
if
(
row
==
null
)
continue
;
Cell
cell
=
row
.
getCell
(
valueCol
);
if
(
cell
==
null
)
continue
;
// 转换单元格值
Object
value
=
convertCellValue
(
cell
,
field
.
getType
(),
excelField
.
dateFormat
());
// 设置字段值
field
.
setAccessible
(
true
);
field
.
set
(
instance
,
value
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"设置字段 {} 值失败: {}"
,
field
.
getName
(),
e
.
getMessage
());
}
}
}
}
/**
* 解析集合字段(增强版,支持动态结束行检测)
* @param sheet
* @param instance
* @throws Exception
*/
private
void
parseCollectionFields
(
Sheet
sheet
,
Object
instance
)
throws
Exception
{
for
(
Field
field
:
instance
.
getClass
().
getDeclaredFields
())
{
ExcelCollection
collectionAnnotation
=
field
.
getAnnotation
(
ExcelCollection
.
class
);
if
(
collectionAnnotation
!=
null
)
{
List
<
Object
>
collectionData
=
new
ArrayList
<>();
// 确定结束行(支持自动检测)
int
endRow
=
collectionAnnotation
.
endRow
()
>=
0
?
collectionAnnotation
.
endRow
()
:
findCollectionEndRow
(
sheet
,
collectionAnnotation
);
// 遍历行,解析集合元素
for
(
int
rowNum
=
collectionAnnotation
.
startRow
();
rowNum
<=
endRow
;
rowNum
++)
{
Row
row
=
sheet
.
getRow
(
rowNum
);
if
(
row
==
null
||
isRowEmpty
(
row
))
{
continue
;
// 跳过空行
}
try
{
Object
element
=
collectionAnnotation
.
type
().
getDeclaredConstructor
().
newInstance
();
boolean
hasData
=
false
;
// 解析元素字段
for
(
Field
elementField
:
collectionAnnotation
.
type
().
getDeclaredFields
())
{
ExcelField
excelField
=
elementField
.
getAnnotation
(
ExcelField
.
class
);
if
(
excelField
!=
null
)
{
// 计算值所在列
int
valueCol
=
excelField
.
valueCol
()
>=
0
?
excelField
.
valueCol
()
:
excelField
.
titleCol
();
Cell
cell
=
row
.
getCell
(
valueCol
);
if
(
cell
!=
null
)
{
Object
value
=
convertCellValue
(
cell
,
elementField
.
getType
(),
excelField
.
dateFormat
());
elementField
.
setAccessible
(
true
);
elementField
.
set
(
element
,
value
);
hasData
=
true
;
}
}
}
if
(
hasData
)
{
collectionData
.
add
(
element
);
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"解析集合元素失败: {}"
,
e
.
getMessage
());
}
}
// 设置集合字段值
field
.
setAccessible
(
true
);
field
.
set
(
instance
,
collectionData
);
}
}
}
/**
* 自动检测集合的结束行
* @param sheet
* @param collectionAnnotation
* @return
*/
private
int
findCollectionEndRow
(
Sheet
sheet
,
ExcelCollection
collectionAnnotation
)
{
int
endRow
=
collectionAnnotation
.
startRow
();
int
maxEmptyRows
=
5
;
// 连续空行的最大数量
// 获取所有需要检查的列
Set
<
Integer
>
columnsToCheck
=
new
HashSet
<>();
for
(
Field
field
:
collectionAnnotation
.
type
().
getDeclaredFields
())
{
ExcelField
excelField
=
field
.
getAnnotation
(
ExcelField
.
class
);
if
(
excelField
!=
null
)
{
int
col
=
excelField
.
valueCol
()
>=
0
?
excelField
.
valueCol
()
:
excelField
.
titleCol
();
columnsToCheck
.
add
(
col
);
}
}
if
(
columnsToCheck
.
isEmpty
())
{
return
sheet
.
getLastRowNum
();
// 如果没有字段注解,返回最后一行
}
int
emptyRowCount
=
0
;
for
(
int
rowNum
=
collectionAnnotation
.
startRow
();
rowNum
<=
sheet
.
getLastRowNum
();
rowNum
++)
{
Row
row
=
sheet
.
getRow
(
rowNum
);
if
(
row
==
null
)
{
emptyRowCount
++;
if
(
emptyRowCount
>=
maxEmptyRows
)
{
return
rowNum
-
maxEmptyRows
;
// 返回连续空行前的最后一行
}
continue
;
}
boolean
hasData
=
false
;
for
(
int
col
:
columnsToCheck
)
{
Cell
cell
=
row
.
getCell
(
col
);
if
(
cell
!=
null
&&
cell
.
getCellType
()
!=
CellType
.
BLANK
)
{
hasData
=
true
;
break
;
}
}
if
(
hasData
)
{
emptyRowCount
=
0
;
endRow
=
rowNum
;
}
else
{
emptyRowCount
++;
if
(
emptyRowCount
>=
maxEmptyRows
)
{
return
endRow
;
// 返回最后有数据的行
}
}
}
return
sheet
.
getLastRowNum
();
}
/**
* 单元格值转换
* @param cell
* @param targetType
* @param dateFormat
* @return
*/
private
Object
convertCellValue
(
Cell
cell
,
Class
<?>
targetType
,
String
dateFormat
)
{
switch
(
cell
.
getCellType
())
{
case
STRING:
return
convertStringValue
(
cell
.
getStringCellValue
().
trim
(),
targetType
,
dateFormat
);
case
NUMERIC:
if
(
DateUtil
.
isCellDateFormatted
(
cell
))
{
return
cell
.
getDateCellValue
();
}
else
{
return
convertNumericValue
(
cell
.
getNumericCellValue
(),
targetType
);
}
case
BOOLEAN:
return
cell
.
getBooleanCellValue
();
case
FORMULA:
try
{
return
convertStringValue
(
cell
.
getStringCellValue
().
trim
(),
targetType
,
dateFormat
);
}
catch
(
Exception
e
)
{
return
cell
.
getCellFormula
();
}
default
:
return
null
;
}
}
private
Object
convertStringValue
(
String
value
,
Class
<?>
targetType
,
String
dateFormat
)
{
if
(
value
==
null
||
value
.
isEmpty
())
return
null
;
if
(
targetType
==
String
.
class
)
{
return
value
;
}
else
if
(
targetType
==
Integer
.
class
||
targetType
==
int
.
class
)
{
return
Integer
.
parseInt
(
value
);
}
else
if
(
targetType
==
Long
.
class
||
targetType
==
long
.
class
)
{
return
Long
.
parseLong
(
value
);
}
else
if
(
targetType
==
Double
.
class
||
targetType
==
double
.
class
)
{
return
Double
.
parseDouble
(
value
);
}
else
if
(
targetType
==
BigDecimal
.
class
)
{
return
new
BigDecimal
(
value
);
}
else
if
(
targetType
==
Date
.
class
)
{
try
{
SimpleDateFormat
sdf
=
new
SimpleDateFormat
(
dateFormat
);
return
sdf
.
parse
(
value
);
}
catch
(
ParseException
e
)
{
log
.
warn
(
"日期格式转换失败: {}"
,
value
);
return
null
;
}
}
else
if
(
targetType
==
Boolean
.
class
||
targetType
==
boolean
.
class
)
{
return
"是"
.
equals
(
value
)
||
"YES"
.
equalsIgnoreCase
(
value
)
||
"TRUE"
.
equalsIgnoreCase
(
value
);
}
return
value
;
}
private
Object
convertNumericValue
(
double
value
,
Class
<?>
targetType
)
{
if
(
targetType
==
Integer
.
class
||
targetType
==
int
.
class
)
{
return
(
int
)
value
;
}
else
if
(
targetType
==
Long
.
class
||
targetType
==
long
.
class
)
{
return
(
long
)
value
;
}
else
if
(
targetType
==
Double
.
class
||
targetType
==
double
.
class
)
{
return
value
;
}
else
if
(
targetType
==
BigDecimal
.
class
)
{
return
BigDecimal
.
valueOf
(
value
);
}
else
if
(
targetType
==
Date
.
class
)
{
return
DateUtil
.
getJavaDate
(
value
);
}
return
value
;
}
private
boolean
isRowEmpty
(
Row
row
)
{
for
(
Cell
cell
:
row
)
{
if
(
cell
!=
null
&&
cell
.
getCellType
()
!=
CellType
.
BLANK
)
{
return
false
;
}
}
return
true
;
}
}
yd-oss-service/src/main/java/com/yd/oss/service/service/impl/ExcelServiceImpl.java
0 → 100644
View file @
a294e8a3
package
com
.
yd
.
oss
.
service
.
service
.
impl
;
import
com.aliyun.oss.OSS
;
import
com.aliyun.oss.model.OSSObject
;
import
com.yd.oss.service.service.ExcelService
;
import
com.yd.oss.service.service.OssService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.poi.ss.usermodel.Workbook
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
/**
* Excel服务实现类
*/
@Slf4j
@Service
public
class
ExcelServiceImpl
implements
ExcelService
{
@Resource
private
OssService
ossService
;
@Autowired
private
OSS
ossClient
;
// 注入OSS客户端
@Autowired
private
String
defaultBucket
;
// 注入默认存储桶
/**
* 从OSS下载模板到临时文件
* @param ossObjectKey
* @return
* @throws IOException
*/
@Override
public
File
downloadTemplateToTempFile
(
String
ossObjectKey
)
throws
IOException
{
// 创建临时文件
Path
tempPath
=
Files
.
createTempFile
(
"template"
,
".xlsx"
);
File
tempFile
=
tempPath
.
toFile
();
tempFile
.
deleteOnExit
();
// JVM退出时删除
try
(
OSSObject
ossObject
=
ossClient
.
getObject
(
defaultBucket
,
ossObjectKey
);
InputStream
inputStream
=
ossObject
.
getObjectContent
();
FileOutputStream
outputStream
=
new
FileOutputStream
(
tempFile
))
{
// 复制流到文件
byte
[]
buffer
=
new
byte
[
1024
];
int
bytesRead
;
while
((
bytesRead
=
inputStream
.
read
(
buffer
))
!=
-
1
)
{
outputStream
.
write
(
buffer
,
0
,
bytesRead
);
}
}
return
tempFile
;
}
/**
* 保存工作簿到临时文件
* @param workbook
* @return
* @throws IOException
*/
@Override
public
File
saveWorkbookToTempFile
(
Workbook
workbook
)
throws
IOException
{
Path
tempPath
=
Files
.
createTempFile
(
"processed"
,
".xlsx"
);
File
tempFile
=
tempPath
.
toFile
();
tempFile
.
deleteOnExit
();
try
(
FileOutputStream
fos
=
new
FileOutputStream
(
tempFile
))
{
workbook
.
write
(
fos
);
}
return
tempFile
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment