专家评审系统总结

赖卓成2023年4月6日
大约 5 分钟

后端

扫包问题

多模块项目,各模块包名不一样,有依赖关系时,由于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

image-20230407002446076

        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();
            }

        }

模板:

image-20230407002507344

图片到处和转为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接口中,MultipartFileFile是一样的,在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...