和原来写过的Django合同台账一样,这一次也要提供一个合同台账的下载功能,其核心就是把项目的合计,合同清单和明细记录清单全部下载下来。这次来一点点实现功能
Java
操作excel
文件的库
Java
用来操作Excel
文件的库是Apache POI
库,其文档在http://poi.apache.org/components/index.html。
xls
和xlsx
文件的操作
微软把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
要快一些。这里有个小插曲,刚部署上去的时候,感觉加载页面特别慢,后来突然想到,应该是每次刷新都从我的服务器加载静态资源的缘故,于是把使用的Bootstrap
和FontAwesome
的链接都改成了CDN
链接,果然发现加载页面的速度基本上和本地差不多了。
FMS
的合同台账功能暂时告一段落了。接下来如果有必要,可能会编写一下自动填表的功能了。
使用jar
包的方式将软件部署在自己的VPS
上
首先需要打一个jar
包,在右侧直接运行maven-lifecycle-package
,能通过测试的话,就会生成jar
包。
之后连接自己的VPS
,先ps -A
找一下已经运行的程序的id
,名称是java
,一般都是比较大的id
,我这一次是1568
和1872
,然后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