Commit da06d17c by zhangxingmin

Merge remote-tracking branch 'origin/dev_zxm' into test

parents 3c19d30b 2baef10c
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);
}
}
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);
}
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解析异常!");
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="ExternalSystem" externalSystem="Maven" />
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots />
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/java" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/../target/classes" />
<output-test url="file://$MODULE_DIR$/../target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
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表示自动检测)
}
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"; // 日期格式
}
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名称
}
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);
}
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;
}
};
}
}
package com.yd.oss.feign.request;
import lombok.Data;
@Data
public class ApiOssExcelParseRequest {
/**
* Sheet对应类的全限定名
*/
private String[] sheetClassNames;
}
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;
}
package com.yd.oss.feign.response;
import lombok.Data;
import java.util.Map;
@Data
public class ApiOssExcelParseResponse {
/**
* 返回解析后的数据
*/
private Map<Integer, Object> map;
}
package com.yd.oss.feign.response;
import lombok.Data;
@Data
public class ApiOssExportAppointmentExcelResponse {
/**
* 访问路径
*/
private String url;
}
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="ExternalSystem" externalSystem="Maven" />
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/../target/classes" />
<output-test url="file://$MODULE_DIR$/../target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
...@@ -107,5 +107,33 @@ ...@@ -107,5 +107,33 @@
<groupId>org.apache.xmlbeans</groupId> <groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId> <artifactId>xmlbeans</artifactId>
</dependency> </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> </dependencies>
</project> </project>
...@@ -18,14 +18,19 @@ public class OssConfig { ...@@ -18,14 +18,19 @@ public class OssConfig {
@Autowired @Autowired
private IOssProviderService ossProviderService; private IOssProviderService ossProviderService;
//默认的OSS提供商
private OssProvider currentProvider; private OssProvider currentProvider;
//默认存储桶名称
private String defaultBucket; private String defaultBucket;
//默认服务端点
private String defaultEndpoint;
@PostConstruct @PostConstruct
public void init() { public void init() {
try { try {
this.currentProvider = ossProviderService.getDefaultProvider(); this.currentProvider = ossProviderService.getDefaultProvider();
this.defaultBucket = currentProvider.getBucketName(); this.defaultBucket = currentProvider.getBucketName();
this.defaultEndpoint = currentProvider.getEndpoint();
log.info("默认OSS提供商初始化成功: {}", currentProvider.getName()); log.info("默认OSS提供商初始化成功: {}", currentProvider.getName());
} catch (Exception e) { } catch (Exception e) {
log.error("默认OSS提供商初始化失败", e); log.error("默认OSS提供商初始化失败", e);
...@@ -49,6 +54,11 @@ public class OssConfig { ...@@ -49,6 +54,11 @@ public class OssConfig {
} }
@Bean @Bean
public String defaultEndpoint() {
return defaultEndpoint;
}
@Bean
public OssProvider currentProvider() { public OssProvider currentProvider() {
return currentProvider; return currentProvider;
} }
......
package com.yd.oss.service.service;
import java.util.Map;
public interface AppointmentExcelService {
String exportAppointment(Map<String, Object> data,
String templateType,
String appointmentBizId);
}
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;
}
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;
}
...@@ -3,7 +3,6 @@ package com.yd.oss.service.service; ...@@ -3,7 +3,6 @@ package com.yd.oss.service.service;
import com.yd.oss.service.dto.FileMetadata; import com.yd.oss.service.dto.FileMetadata;
import com.yd.oss.service.dto.UploadResult; import com.yd.oss.service.dto.UploadResult;
import com.yd.oss.service.model.OssProvider; import com.yd.oss.service.model.OssProvider;
import java.io.InputStream; import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
...@@ -65,5 +64,9 @@ public interface OssService { ...@@ -65,5 +64,9 @@ public interface OssService {
// 获取上传结果(包含文件信息和访问URL) // 获取上传结果(包含文件信息和访问URL)
UploadResult getUploadResult(String fileKey, Duration expiration); UploadResult getUploadResult(String fileKey, Duration expiration);
String upload(byte[] content, String fileName);
String upload(InputStream inputStream, String fileName);
} }
...@@ -2,6 +2,7 @@ package com.yd.oss.service.service.impl; ...@@ -2,6 +2,7 @@ package com.yd.oss.service.service.impl;
import com.aliyun.oss.OSS; import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.OSSObject; import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest; import com.aliyun.oss.model.PutObjectRequest;
...@@ -49,6 +50,9 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -49,6 +50,9 @@ public class AliYunOssServiceImpl implements OssService {
private String defaultBucket; // 注入默认存储桶 private String defaultBucket; // 注入默认存储桶
@Autowired @Autowired
private String defaultEndpoint; // 注入默认服务端点
@Autowired
private OssProvider currentProvider; // 注入当前提供商 private OssProvider currentProvider; // 注入当前提供商
/** /**
...@@ -714,4 +718,72 @@ public class AliYunOssServiceImpl implements OssService { ...@@ -714,4 +718,72 @@ public class AliYunOssServiceImpl implements OssService {
file.setId(-1L); // 使用无效ID file.setId(-1L); // 使用无效ID
return file; 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);
}
}
} }
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;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="ExternalSystem" externalSystem="Maven" />
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots />
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/java" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/../target/classes" />
<output-test url="file://$MODULE_DIR$/../target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4" /> <module type="JAVA_MODULE" version="4">
\ No newline at end of file <component name="ExternalSystem" externalSystem="Maven" />
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment