Fms-Java版开发实录:38 下载合同台账功能与简单部署

Fms-Java版开发实录:38 下载合同台账功能与简单部署

合同台账快完工了,现在写上最后一个重要功能:下载合同台账

和原来写过的Django合同台账一样,这一次也要提供一个合同台账的下载功能,其核心就是把项目的合计,合同清单和明细记录清单全部下载下来。这次来一点点实现功能

Java操作excel文件的库

Java用来操作Excel文件的库是Apache POI库,其文档在http://poi.apache.org/components/index.html

xlsxlsx文件的操作

微软把Excel搞出来两个文件格式,在实践中其实很不爽。很多库也必须同时兼容两种文件的操作。POI库中提供了两个类,HSSF用于Excel 97-2007的版本,XSSF用于Excel 2007之后的xlsx文件。对于我来说,当然就选择XSSF库了。

Quickguide中提供了很多例子,可用来快速上手。对于创建和写入一个Excel文件来说难度不大,要解决的核心问题是将内存中的文件写入Http响应中返回给前端,这个问题类似于我在Django版本中要解决的问题。

编写下载合同台账功能

先来尝试编写一个返回excel文件的控制器,如果成功,再开始写返回逻辑。

在项目中添加依赖

在官网上看到2021年1月,POI已经更新到了5.0.0版本。在maven上找到依赖地址如下:

<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.0.0</version>
</dependency>

还需要导入:

<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.0.0</version>
</dependency>

编写业务层

这里需要先搞清楚如何返回一个下载文件。实际上和原来的Django项目类似,也是需要返回一个二进制流,然后设置上对应的HTTP响应头,就可以让浏览器下载文件了。

这里我进行了拆分,把组装二进制流的功能留在业务层,让控制器只负责返回响应即可。

业务层的代码其实很简单,就是逐个读取所有的数据库,计算出对应的BO,然后把数据逐条写进去即可,这里只需要注意合同的部分内容是null,做一个判断即可。

以返回最复杂的合同列表数据为例:

@Service
public class DownloadService {

    private final JournalService journalService;
    private final ContractService contractService;
    private final SubjectService subjectService;
    private final ProjectService projectService;

    @Autowired
    public DownloadService(JournalService journalService, ContractService contractService, SubjectService subjectService, ProjectService projectService) {
        this.journalService = journalService;
        this.contractService = contractService;
        this.subjectService = subjectService;
        this.projectService = projectService;
    }

    @Transactional
    public ResponseEntity<byte[]> getDownloadData() {
        // 创建两个fomatter用于一会写入日期数据和组装凭证号数据
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat formatMonth = new SimpleDateFormat("yyyy-MM");

        // 创建一个工作簿
        XSSFWorkbook result = new XSSFWorkbook();


        // 在工作簿中创建合同列表工作表并写入数据
        XSSFSheet sheet3 = result.createSheet("合同列表");
        List<ContractData> contractDataList = new ArrayList<>();
        contractService.findAllContracts().forEach(contract -> contractDataList.add(contractService.getContractDataById(contract.getId())));
        
        //在新工作表中写入表头,也就是第一行的数据
        XSSFRow headerRow3 = sheet3.createRow(0);
        headerRow3.createCell(0).setCellValue("合同编号");
        headerRow3.createCell(1).setCellValue("印花税比例");
        headerRow3.createCell(2).setCellValue("合同名称");
        headerRow3.createCell(3).setCellValue("所属项目");
        headerRow3.createCell(4).setCellValue("所属类别");
        headerRow3.createCell(5).setCellValue("主要条款");
        headerRow3.createCell(6).setCellValue("合同方1");
        headerRow3.createCell(7).setCellValue("合同方2");
        headerRow3.createCell(8).setCellValue("合同方3");
        headerRow3.createCell(9).setCellValue("合同方4");
        headerRow3.createCell(10).setCellValue("合同总价");
        headerRow3.createCell(11).setCellValue("不含税价格");
        headerRow3.createCell(12).setCellValue("增值税额");
        headerRow3.createCell(13).setCellValue("结算价格");
        headerRow3.createCell(14).setCellValue("签订时间");
        headerRow3.createCell(15).setCellValue("终止时间");
        headerRow3.createCell(16).setCellValue("合同标的");
        headerRow3.createCell(17).setCellValue("联系人");
        headerRow3.createCell(18).setCellValue("联系电话");
        headerRow3.createCell(19).setCellValue("资金收付");
        headerRow3.createCell(20).setCellValue("开发成本");
        headerRow3.createCell(21).setCellValue("预付账款");
        headerRow3.createCell(22).setCellValue("进项税额");
        headerRow3.createCell(23).setCellValue("其他资产");
        headerRow3.createCell(24).setCellValue("应付账款");
        headerRow3.createCell(25).setCellValue("其他应付款");
        headerRow3.createCell(26).setCellValue("其他负债");
        headerRow3.createCell(27).setCellValue("其他损益");

        // 用循环写入所有需要写入的数据
        for (int i = 0; i < contractDataList.size(); i++) {
            XSSFRow row = sheet3.createRow(i + 1);
            ContractData contractData = contractDataList.get(i);
            row.createCell(0).setCellValue(contractData.getContract().getNumber());
            row.createCell(1).setCellValue(contractData.getContract().getStamp().getRate().toString());
            row.createCell(2).setCellValue(contractData.getContract().getName());
            row.createCell(3).setCellValue(contractData.getContract().getSubject().getProject().getProjectName());
            row.createCell(4).setCellValue(contractData.getContract().getSubject().getSubjectName());
            row.createCell(5).setCellValue(contractData.getContract().getTerms());
            row.createCell(6).setCellValue(contractData.getContract().getSupplier());
            row.createCell(7).setCellValue(contractData.getContract().getSecondarySupplier1());
            row.createCell(8).setCellValue(contractData.getContract().getSecondarySupplier2());
            row.createCell(9).setCellValue(contractData.getContract().getSecondarySupplier3());
            row.createCell(10).setCellValue(contractData.getContract().getTotalPrice().toString());
            row.createCell(11).setCellValue(contractData.getContract().getPriceWithoutVAT() == null ? "0.00" : contractData.getContract().getPriceWithoutVAT().toString());
            row.createCell(12).setCellValue(contractData.getContract().getVAT() == null ? "0.00" : contractData.getContract().getVAT().toString());
            row.createCell(13).setCellValue(contractData.getContract().getFinalPrice() == null ? "0.00" : contractData.getContract().getFinalPrice().toString());
            row.createCell(14).setCellValue(contractData.getContract().getSignDate() == null ? "" : format.format(contractData.getContract().getSignDate()));
            row.createCell(15).setCellValue(contractData.getContract().getEndDate() == null ? "" : format.format(contractData.getContract().getEndDate()));
            row.createCell(16).setCellValue(contractData.getContract().getTarget());
            row.createCell(17).setCellValue(contractData.getContract().getContact());
            row.createCell(18).setCellValue(contractData.getContract().getPhone());
            row.createCell(19).setCellValue(contractData.getCash().toString());
            row.createCell(20).setCellValue(contractData.getDevelopmentCost().toString());
            row.createCell(21).setCellValue(contractData.getPrepaid().toString());
            row.createCell(22).setCellValue(contractData.getInputVAT().toString());
            row.createCell(23).setCellValue(contractData.getOtherAsset().toString());
            row.createCell(24).setCellValue(contractData.getAccountPayable().toString());
            row.createCell(25).setCellValue(contractData.getOtherPayable().toString());
            row.createCell(26).setCellValue(contractData.getOtherLiability().toString());
            row.createCell(27).setCellValue(contractData.getOtherPL().toString());
        }

        // 创建用于返回的字节流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        
        // 把生成的工作表写入到字节流中并且关闭
        try (outputStream) {
            result.write(outputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 设置HTTP响应头中的类型和文件名等数据
        HttpHeaders httpHeaders = new HttpHeaders();

        Date date = new Date();
        String fileName = new String(("合同台账" + format.format(date) + ".xlsx").getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);

        httpHeaders.setContentDispositionFormData("attachment", fileName);
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);

        // 返回制作好的ResponseEntity对象
        return new ResponseEntity<>(outputStream.toByteArray(), httpHeaders, HttpStatus.CREATED);
}

其他几个表的写入代码也很类似,代码只是长而已,没任何难度。

编写控制器

控制器就简单多了,现在我的路径为/pms/download/all,意思是下载全部,如果未来还需要下载某个合同或者其他具体的内容,到时候可以再添加,这个下载功能直接放到了下拉菜单中。

控制器的具体代码是:

package cc.conyli.fms.controller.pms;

import cc.conyli.fms.service.pms.DownloadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.IOException;

@Controller
@RequestMapping("/pms/download")
public class DownloadController {

    private final DownloadService downloadService;

    @Autowired
    public DownloadController(DownloadService downloadService) {
        this.downloadService = downloadService;
    }

    @GetMapping("/all")
    public ResponseEntity<byte[]> downloadAll() throws IOException {

        return downloadService.getDownloadData();
    }
}

非常简单。下载功能就做好了,点击下载功能就能得到最新的合同台账,里边的数字全部是类似银行生成的表一样采用字符串写入,保证精准。

然后又进行了一些样式的修改,现在终于可以把版本号改成1.0.0了,跑在自己的域名上,实验了一下,感觉Java确实比Python要快一些。这里有个小插曲,刚部署上去的时候,感觉加载页面特别慢,后来突然想到,应该是每次刷新都从我的服务器加载静态资源的缘故,于是把使用的BootstrapFontAwesome的链接都改成了CDN链接,果然发现加载页面的速度基本上和本地差不多了。

FMS的合同台账功能暂时告一段落了。接下来如果有必要,可能会编写一下自动填表的功能了。

使用jar包的方式将软件部署在自己的VPS

首先需要打一个jar包,在右侧直接运行maven-lifecycle-package,能通过测试的话,就会生成jar包。

之后连接自己的VPS,先ps -A找一下已经运行的程序的id,名称是java,一般都是比较大的id,我这一次是15681872,然后kill之。干掉之后确认页面已经打不开。

通过FTP连接,把jar包上传到自己的目录里,我放在/root/fms中。

当然,在运行之前,还需要配置好java 11,我使用的是官方的源。

jar包所在的目录,运行如下命令:

nohup java -jar fms-java-1.0.0.jar &

之后等个几秒钟,等程序启动起来,可以在同目录下边的nohup.out看到启动信息。之后就可以访问网站了。

运行起来之后,还得注意经常备份网站数据,备份数据的文章在此

我的博客(也就是现在看到的这个也使用了Java),为了避免错杀,可以使用:

systemctl status halo

来查看是哪一个PID。如果真杀错了,使用如下命令重新启动博客:

systemctl start halo
LICENSED UNDER CC BY-NC-SA 4.0
Comment