专家评审系统总结
2023年4月6日
后端
扫包问题
多模块项目,各模块包名不一样,有依赖关系时,由于SprintBoot自动配置扫描启动类所在包及其子包,其他模块扫不进来,需要添加扫包注解:
@ComponentScan(basePackages = {"com.huilian.expert.api","com.huilian.expert.common"})
@MapperScan({"com.huilian.expert.api.mapper","com.huilian.expert.common.mapper"})
stream按时间排序
虽然没有内置的时间的比较器,但是可以手动传入:使用匿名内部类或λ表达式实现自定义比较器
.sorted((f1, f2) -> f2.getCreateTime().compareTo(f1.getCreateTime()))
stream集合元素字符串分隔符连接:
String fileIds = r.getMaterialList().stream().map(BaseMaterialVo::getId).distinct().collect(Collectors.joining(ExpertAuditConstant.SEPARATOR_TABLE_FILED));
stream之flatMap
Set<BizExpertMaterial> set = Stream.of(resultMaterial, certMaterial, resumeMaterial, workMaterial, jobMaterial)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
便于理解。拆分:
// 创建Stream,泛型为List<BizExpertMaterial>
Stream<List<BizExpertMaterial>> stream = Stream.of(resultMaterial, certMaterial, resumeMaterial, workMaterial, jobMaterial);
// 使用map,获取stream中的五个List的stream,可以发现得到的是Stream<Stream<BizExpertMaterial>>
Stream<Stream<BizExpertMaterial>> streamStream = stream.map(Collection::stream);
// 使用flatMap,可以将Stream<Stream<BizExpertMaterial>>内层的五个Stream<BizExpertMaterial>合并,即下一行得到的Stream<BizExpertMaterial>
Stream<BizExpertMaterial> materialStream = stream.flatMap(Collection::stream);
// 最后,收集
List<BizExpertMaterial> collect = materialStream.collect(Collectors.toList());
EasyExcel填充导出列表、填充图片、转pdf
// 转pdf
try {
// 模板文件路径和导出文件路径
ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 列表
excelWriter.fill(new FillWrapper("resume", filledInfo.getResumeList()), writeSheet);
excelWriter.fill(new FillWrapper("work", filledInfo.getWorkList()), writeSheet);
excelWriter.fill(new FillWrapper("job", filledInfo.getJobList()), writeSheet);
excelWriter.fill(new FillWrapper("cert", filledInfo.getCertList()), writeSheet);
excelWriter.fill(new FillWrapper("result", filledInfo.getResultList()), writeSheet);
excelWriter.fill(dtoBaseInfo, writeSheet);
excelWriter.close();
// 图片填充
Workbook workbook = new Workbook(fileName);
WorksheetCollection worksheets = workbook.getWorksheets();
// 获取第一个工作簿
Worksheet worksheet = worksheets.get(0);
ShapeCollection shapes = worksheet.getShapes();
// 的插入形状
com.aspose.cells.Shape shape = shapes.addShape(MsoDrawingType.RECTANGLE, 1,1,6,1,175, 116);
// 字节数组
shape.getFill().setImageData(avatarBytes);
// 转为pdf
workbook.save(pdfFileName, new PdfSaveOptions());
FileExportUtil.download(httpResponse, pdfFileName, "专家登记表.pdf");
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(BusinessExceptionEnum.EXPORT_PDF_FAIL);
} finally {
// 删除这两个临时文件
File excel = new File(fileName);
File pdf = new File(pdfFileName);
if (excel.exists()) {
excel.delete();
}
if (pdf.exists()) {
pdf.delete();
}
}
模板:
图片到处和转为pdf使用破解的jar包:
aspose,未测试:点击跳转
已测试可用:点击跳转
feign配置类中添加请求头、设置超时时间
/**
* feign配置
*
* @author 赖卓成
* @date 2023/03/15
*/
@Configuration
public class FeignConfig implements RequestInterceptor {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
@Bean
public Request.Options options(){
return new Request.Options(15000,14000);
}
@Autowired
private ApplicationProperties applicationProperties;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(applicationProperties.getJwt().getHeader(), UserThreadLocalContext.getClientJwt());
}
}
feign上传文件
注意@PostMapping
注解需要consumes参数,方法参数需要@RequestPart
注解指定文件名
/**
* 上传
*
* @param file 文件
* @return {@link Result}<{@link Void}>
*/
@PostMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Result<FileUploadDto> upload(@RequestPart(name = "file") MultipartFile file);
feign接口也可以是:
/**
* 上传文件
*
* @param file 文件
* @param bizType 业务类型
* @param bizId 业务id
* @return {@link Result}<{@link FileUploadDto}>
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Result<FileUploadDto> uploadFile(@RequestPart("file") File file,@RequestParam("bizType") String bizType,@RequestParam("bizId") String bizId);
在feign接口中,MultipartFile
和File
是一样的,在controller层不用再另外写一个接口了。
feign下载文件
接口:返回值设置为ResponseEntity<Resource>
@GetMapping("/download")
@ApiOperation("文件服务-根据路径下载")
public ResponseEntity<Resource> getByPath(@RequestParam("path") String path) {
return localStorageService.getByPath(path);
}
实现类:
@Override
public ResponseEntity<Resource> getByPath(String filePath) {
String parentPath = applicationProperties.getFile().getPath();
filePath = parentPath + ExpertAuditConstant.PATH_SEPARATOR + filePath;
Path path = Paths.get(filePath);
byte[] fileBytes = new byte[0];
try {
fileBytes = Files.readAllBytes(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
ByteArrayResource resource = new ByteArrayResource(fileBytes);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=sample" + filePath.substring(filePath.lastIndexOf(".")));
// 获取文件的扩展名
String fileExtension = FilenameUtils.getExtension(path.toString());
// 根据文件类型设置Content-Type值
switch (fileExtension.toLowerCase()) {
case "txt":
headers.setContentType(MediaType.TEXT_PLAIN);
break;
case "pdf":
headers.setContentType(MediaType.APPLICATION_PDF);
break;
case "jpg":
case "jpeg":
headers.setContentType(MediaType.IMAGE_JPEG);
break;
case "png":
headers.setContentType(MediaType.IMAGE_PNG);
break;
default:
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
break;
}
return ResponseEntity.ok()
.headers(headers)
.contentLength(fileBytes.length)
.body(resource);
}
读取文件字节数组后,创建
ByteArrayResource
,获取文件名和扩展名放响应头,响应体放字节数组ByteArrayResource
。
消费者:
ResponseEntity<org.springframework.core.io.Resource> response = fileFeign.getByPath(file.getFilePath());
byte [] avatarBytes = null;
try {
org.springframework.core.io.Resource body = response.getBody();
System.out.println(body);
InputStream is = body.getInputStream();
// java.io.InputStream.available返回此输入流方法的下一个调用方可以不受阻塞地从此输入流读取(或跳过)的字节数
avatarBytes = new byte [is.available()];
is.read(avatarBytes);
System.out.println(new String(avatarBytes));
} catch (Exception e) {
throw new RuntimeException(e);
}
获取响应的InputStream,读到字节数组中即可。
restTemplate上传文件:
public DzqzUploadFileResponseDto uploadFile(File file){
HttpHeaders headers = new HttpHeaders();
// 无法判断token有效期,所以每次都获取一遍token
String accessToken = this.getAccessToken().getData().getAccessToken();
headers.set("x-tif-jwt",accessToken);
headers.set("Content-Type", "multipart/form-data");
FileSystemResource resource = new FileSystemResource(file);
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", resource);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(param,headers);
String url = applicationProperties.getDzqz().getUrl();
ResponseEntity<String> responseEntity = restTemplate.exchange(url +UPLOAD_FILE_PATH, HttpMethod.POST, httpEntity, String.class);
String json = responseEntity.getBody();
System.out.println();
DzqzUploadFileResponseDto fileResponseDto;
try {
fileResponseDto = objectMapper.readValue(json, DzqzUploadFileResponseDto.class);
if (Objects.isNull(fileResponseDto)||fileResponseDto.getErrCode()!=0){
throw new RuntimeException(fileResponseDto.getErrMsg());
}
} catch (Exception e) {
// TODO 魔法值、异常枚举
throw new BusinessException("上传文件失败:"+e.getMessage());
}
return fileResponseDto;
}
restTemplate下载文件
public byte[] downloadFile(String fileId){
// 无法判断token有效期,所以每次都获取一遍token
String accessToken = this.getAccessToken().getData().getAccessToken();
String url = applicationProperties.getDzqz().getUrl();
RequestEntity<Void> requestEntity = RequestEntity
.get(url + DOWNLOAD_FILE_PATH+"?fileid="+fileId)
.header("x-tif-jwt", accessToken)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.build();
ResponseEntity<byte[]> entity = restTemplate.exchange(requestEntity, byte[].class);
byte[] body = entity.getBody();
if (body.length==0){
throw new BusinessException("下载文件失败");
}
return body;
}
下载后转为File对象:
byte[] bytes = dzqzUtil.downloadFile(orderStatus.getData().getIds().get(0).getSignFileId());
File signFile = new File(applicationProperties.getExport().getExportPath() + "template_" + System.currentTimeMillis() + "_group_" + id + "_sign_.pdf");
try (FileOutputStream fos = new FileOutputStream(signFile)) {
fos.write(bytes);
} catch (IOException e) {
// TODO 异常枚举
e.printStackTrace();
throw new BusinessException("获取最新签到表失败!");
}
前端
打包
打包后文件丢失问题
假如pom.xml不加resources,编译出来的会丢失文件,这也就是很多情况下,我们在本地电脑运行没有问题,但是一旦打包到服务器,会发现项目根本跑不起来,缺少各种文件,有时候就是这个原因。
解决:添加resources进行解决
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
打包后jar包丢失问题
多模块打包没有将本地jar打包,需要包含本地jar包,如common,等。
在打包插件添加:includeSystemScope
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<mainClass>com.huilian.expert.api.ExpertApiApplication</mainClass>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
springboot项目打war包
参考:点击跳转
pom修改:
<packaging>war</packaging>
启动类继承SpringBootServletInitializer
重写方法:
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExpertApiApplication.class);
}
打war包后,resource下的lib中的本地jar丢失,添加插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>src/main/resources/lib</directory>
<targetPath>WEB-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
Loading...