我们的一个产品是做医疗数据分析统计的,核心功能是通过精细的数据过滤条件及灵活的组合方式,给予用户使用严苛条件检索出病人数据,而数据分析这块,我们是通过 R 语言库,编码实现常用的统计分析功能。
有个客户可能是习惯于使用 IBM SPSS 分析软件,提出了筛选出来的病人数据直接进入 SPSS,然后他们就可以在 SPSS 里面做任何分析。对我们而言就是怎么让数据生成好后,用户在我们的应用里一键启动 SPSS 并且数据自动显现在 SPSS 的数据窗口。
很显然,如果 SPSS 不提供相关接口,我们没办法做到。好在 SPSS 在版本 21 里提供了一个 javaspssplugin.jar, 有了它,调用 API 就可以在不以 page mode 方式启动 SPSS 下使用其强大的统计分析功能,其实就是每次都要通过 statsXD.exe 打开一个 spss 后台进程,提交数据,完了得到分析结果,最后结束进程。
这段代码就是启动 SPSS 进程的逻辑,StartXD.exe 就是启动后台进程的可执行文件。而 SPSS 的 stats.exe 是启动 page mode 的 SPSS 进程的可执行文件,那么我是否可以手写一个类似的代码,这样就打开了 SPSS 桌面应用的窗口,并且数据传过去了呢?
事情并没有那么简单,通过 Eclipse 的反编译插件,可以看到代码的封装的非常好,不管是属性,方法,还是类,都没有办法继承,或以某种方式修改关键属性。干脆,我直接反编译代码,新建一个 java 工程,添加一个我自己的类,然后重新打包。可是试了几种反编译工具,都无法得到一个没有编译错误的工程代码。
这条路走不通,的确有点沮丧,但是忽然想到了 StartXD.exe 后面可以带参数,那么 stats.exe 是不是也可以呢?终于找到了 SPSS Command line options ,剩下的就简单了,我下载一个 SPSS,然后写个 csv 数据文件,一个 main 方法,里面使用
Runtime.getRuntime().exec("D:/software/SPSS/stats.exe D:/data/abc.csv")
执行SPSS命令,很快搞定。
然而又有新问题了,一般地,我们的应用是部署在客户的服务器上的,用户使用浏览器访问这个应用,而 SPSS 桌面应用用户会在本地安装一个。由于数据文件是写在服务端的,这个肯定要传给用户使用,只能是通过浏览器下载,这个不难。但是下载后,怎么让浏览器感知相关文件下载完毕,并且能像我们 main 方法里那样调用操作系统命令呢?
看来浏览器不但要下载数据文件,还得下载个 bat 文件啊,不然没办法执行命令啊。恩,的确 IE 浏览器可以通过 ActiveXObject.run() 执行 bat 文件。可是,我们的应用不管是开发还是部署到客户那边,都是推荐使用 chrome 的,貌似 chrome 没有类似 API 啊,假设也有吧。那么剩下的就是 bat 里面怎么写浏览器所在机器 SPSS 的安装路径?
是不是可以在用户下载数据文件的页面提供一个控件,让用户自己提交 SPSS 的安装目录?显然,有些用户可能不知道或忘记了这个软件装在哪,或者直接把桌面的快捷方式地址提供了,如果提供的是快捷方式,网上倒是有相关解决方法,帮助找到真实的 SPSS 安装路径,但代码稍多,不好维护。如果在 bat 里通过 find 命令啥的找,那太费时了,不现实。这些问题其实很快就被否定了,不能往这个路上走。
最后发现,只需要把文件数据文件写成 SPSS 的数据文件也就是 .SAV 文件(要用到 spssw-**.jar 的 CsvToSPSS.class),然后 chrome 开启 “总是打开此类文件” 就可以做到下载完毕后,直接启动 SPSS 桌面应用并打开数据文件。
try (BufferedInputStream input = new BufferedInputStream(new ByteArrayInputStream(matrix.getBytes()));
BufferedOutputStream ops = new BufferedOutputStream(resp.getOutputStream())) {
resp.setContentType("application/x-spss-sav;charset=UTF-8");
String fName = StringUtils.isEmpty(fileName) ? UUID.randomUUID().toString().substring(0, 8) : fileName;
resp.addHeader("Content-Disposition", "attachment;filename=" + fName + ".sav");
resp.setCharacterEncoding("UTF-8");
CsvToSPSS.convert(input, ops, "UTF-8");
ops.flush();
}
一通下来,算是峰回路转吧,只是为什么一开始就没想到这个最简单的方式呢。可能只想着所有的东西全部自动化,用户不需要做任何操作,任何设置,一切通过编程的方式执行,最关键的是 SPSS 可以打开 CSV文件(会弹框询问CSV格式),这个下载完后默认的打开方式是 Excel 的,这些因素限制了思维的发散性。
=========update on 2019/11/25============
上面的方案能够解决数据导出并自动启动SPSS软件,但是出现了两个问题:
1. 所有的数据,都是变成字符串类型了,用户需要在SPSS里手工修改数据类型
2. 表头单独占用了一行,而不是常规的 .sav 在 SPSS 软件里的展示,这个格式还是不够友好,需要用户调整
解决方案是:
1. 借助 R 语言,我们用的是 <groupId>com.github.jbytecode</groupId> <artifactId>RCaller</artifactId> 来处理,
也就是把数据导入到 R,建立一个 data frame
2. 注意,默认情况下,data frame 里的字符串变量都会被识别成 Factor,需要对变量数据类型进行修改,
如 df$Name <- as.character(df$Name)
3. 使用 write_sav 函数将数据框数据写到临时的 .sav 文件
4. 最后 java 读取 .sav 文件,以流传给浏览器
将要导出的数据落盘到临时的 df.sav 的 R 代码:
RCaller caller = initRCaller(new RCallerTemplate() {
@Override
public void addRCode(RCode code) {
code.addStringArray("Name", names);
code.addDoubleArray("Score", scores);
code.addRCode("df <- data.frame(Score, Name)");
code.addRCode("df$Name <- as.character(df$Name)");
code.addRCode("write_sav(df, "<temp_dir>/df.sav")");
}
});
caller.runOnly();