packagecom.xxxx.modules.api.controller;importcom.alibaba.fastjson.JSONObject;importcom.baomidou.mybatisplus.core.toolkit.IdWorker;importcom.deepoove.poi.XWPFTemplate;importcom.deepoove.poi.config.Configure;importcom.xxxx.common.utils.FreemarkerUtil;importcom.xxxx.common.utils.PoiUtil;importcom.xxxx.common.utils.ZipUtil;importcom.xxxx.modules.constant.ApiConsts;importcom.xxxx.modules.framework.PendingJobPool;importcom.xxxx.modules.framework.vo.TaskResult;importcom.xxxx.modules.framework.vo.TaskResultType;importcom.xxxx.modules.heath.dto.TCPatientsDTO;importcom.xxxx.modules.heath.dto.TCPhsUserDTO;importcom.xxxx.modules.heath.dto.TCTransportLogDTO;importcom.xxxx.modules.heath.entity.TCTransportLogEntity;import com.xxxx.modules.heath.service.*;importcom.xxxx.modules.jt.service.SingleTablePolicy;importcom.xxxx.modules.jt.service.WordService;importcom.xxxx.modules.oss.cloud.OSSFactory;importfreemarker.template.Template;importio.renren.common.annotation.LogOperation;importio.renren.common.constant.Constant;importio.renren.common.utils.ConvertUtils;importio.renren.common.utils.Result;importio.swagger.annotations.Api;importio.swagger.annotations.ApiOperation;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.collections.MapUtils;importorg.apache.commons.lang3.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;import java.io.*;importjava.nio.charset.StandardCharsets;import java.util.*;import java.util.concurrent.*;importjava.util.stream.Collectors;importjava.util.zip.Adler32;importjava.util.zip.CheckedOutputStream;importjava.util.zip.ZipOutputStream;
@Slf4jpublic classClientController {//缓存批量下载的jobName
public static Map batchJobNameCache = new HashMap<>();//取得机器的cpu数量
public static final int THREAD_COUNTS =Runtime.getRuntime().availableProcessors();public static ExecutorService docMakePool = Executors.newFixedThreadPool(THREAD_COUNTS*2);@GetMapping("xxxx")
@ApiOperation("批量下载")
@LogOperation("批量下载")
@ResponseBodypublic Result batchDownloadPlanscode(@RequestParam Map params,HttpServletRequest request) throwsException {//参数判断
//判断任务是否已经存在(防重复)
String jobName = "xxxx";
String jobNameExist= MapUtils.getString(batchJobNameCache, jobName, "");if(StringUtils.isNotEmpty(jobNameExist)){
log.info(tipStr+ "下载正在处理中,请耐心等待~");return new Result().error(201, tipStr + "下载正在处理中,请耐心等待~");
}//1、数据库取模板路径,获得模板实例
String url =xxxxService.getTemplateUrl(planCode, downType);if(StringUtils.isEmpty(url)){
log.info("模板url为空");return new Result().error(202, "请先指定模板~");
}//本次任务显示的中文名称(作为客户端显示)——根据实际需求,看是否需要,可以是客户端传参
String downName = "xxxx";//2、这个根据项目实际需求
List list =xxxxxService.getByPlansCode(planCode);if(list.isEmpty()){
log.info("没有可下载的数据");return new Result().error(201, "暂无该场次报告数据!");
}//本批次任务记录的主键
Long id =IdWorker.getId();
batchJobNameCache.put(jobName,0);//3、另起一个线程处理下载任务(重要)
new Thread(newAsynMakeDoc(id, jobName, list, url)).start();//4、记录下载痕迹
TCTransportLogDTO dto = newTCTransportLogDTO();
dto.setId(id);
dto.setUserName(phsUser.getUserName());//当前用户
dto.setBusinessType(ApiConsts.TRANSMISSION_UPLOAD);//上传(生成报告-打zip包-上传oss)
dto.setStatus(1);//有效
dto.setJobName(jobName);//批次任务唯一标识
dto.setResultName(downName);//批次任务中文名称,作为下载记录的显示在客户端
dto.setJobType(0);//0-批次,1-子任务
dto.setCreateDate(newDate());
tcTransportLogService.insert(ConvertUtils.sourceToTarget(dto, TCTransportLogEntity.class));return new Result().success(200, "添加下载新任务成功~", jobName + "," +id);
}/*** 异步处理word生成*/
class AsynMakeDoc implementsRunnable{private Long id;//主键
private String jobName;//场次号_档案_用户
private Listlist;privateString templateUrl;public AsynMakeDoc(Long id, String jobName, Listlist, String templateUrl) {this.jobName =jobName;this.list =list;this.downType =downType;this.templateUrl =templateUrl;this.id =id;
}
@Overridepublic voidrun() {
Object template;//取oss模板的后缀
String templateType =wordService.getType(templateUrl);if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){
templateType=ApiConsts.TEMPLATE_TYPE_XML;
template=FreemarkerUtil.getTemplate(templateUrl);
}else{
templateType=ApiConsts.TEMPLATE_TYPE_POI;//多个自定义渲染策略
Configure configures =Configure.createDefault();
configures.customPolicy("urines", new SingleTablePolicy(1, 5));
configures.customPolicy("bloods", new SingleTablePolicy(1, 5));
configures.customPolicy("examines", new SingleTablePolicy(1, 5));
template=PoiUtil.getTemplate(templateUrl, configures);
}String fileName= "_报告.doc";
String zipName= "_报告.zip";String plansCode= list.get(0).getPlansCode();//报告、压缩包临时路径
String outTempPath = "";
String zipPath= "";//更新批次下载记录状态
TCTransportLogEntity dto = newTCTransportLogEntity();
dto.setId(id);
dto.setJobType(1);//不管成功失败,批次任务变更为子任务
dto.setBusinessType(ApiConsts.TRANSMISSION_DOWNLOAD);//变更为下载
File zipFile= null;
ZipOutputStream zos= null;//生成目标文件对象的输出流
OutputStream outputStream = null;try{//临时压缩包目录:本地磁盘/jobName.zip(jobName需保证多用户并发时候不会相互干扰)
outTempPath =getTempPath();
zipPath= outTempPath + jobName + ".zip";
zipFile= newFile(zipPath);
log.info("temporary zip :" +zipPath);
outputStream= newFileOutputStream(zipPath);
CheckedOutputStream cos= new CheckedOutputStream(outputStream, newAdler32());//生成ZipOutputStream,用于写入要压缩的文件
zos = newZipOutputStream(cos);//1、往线程池添加任务
log.info(" start generating words...");
CompletionService docCompletionService = new ExecutorCompletionService(docMakePool);for (int i = 0; i < list.size(); i++) {
docCompletionService.submit(new DocMakeTask(list.get(i), fileName, template, outTempPath +jobName, templateType));
}//计算已打成完成数量
int zipCount = 0;//2、从线程池取执行结果进行压缩
for (int j = 0; j < list.size(); j++) {//阻塞取结果
Future future =docCompletionService.take();//判断要压缩的源文件是否存在
String path =future.get();if (!StringUtils.isEmpty(path)) {
File sourceFile= newFile(path);if (!sourceFile.exists()) {throw new RuntimeException("[" + sourceFile + "] is not exists ...");
}
ZipUtil.compressFile(sourceFile, zos, sourceFile.getName(),true);if(sourceFile.exists()) {
sourceFile.delete();
}
zipCount++;//通过应用缓存更新处理进度
log.info("压缩进度:" + zipCount + "/" + list.size() + " : " + zipCount*100/list.size());
batchJobNameCache.put(jobName, zipCount*100/list.size());
}
}//关闭压缩流(不然上传的文件是不完整的)
zos.finish();
zos.close();
outputStream.close();long s1 =System.currentTimeMillis();
log.info(jobName+ ".zip completed,耗时:" + (s1 -start));//删除临时文件夹
File docTempDir = new File(outTempPath +jobName);if(docTempDir.exists()){
docTempDir.delete();
log.info(" temporary folder " + jobName + " has been deleted ");
}
log.info(" ready to upload ");//3、压缩包上传oss,路径自定义:场次号/场次号_healthy.zip
FileInputStream inputStream = null;
String ossPathName= downType + "/" + plansCode + "/"+ System.currentTimeMillis() + "/" + plansCode +zipName;
String ossPath= "";try{
inputStream= newFileInputStream(zipPath);
ossPath=OSSFactory.build().upload(inputStream, ossPathName);
batchJobNameCache.put(jobName+ "_ossPath", ossPath);
log.info("upload complete , ossPath:" +ossPath);
dto.setResultReturn(ossPath);
dto.setResultType(String.valueOf(TaskResultType.Success));
}catch(Exception e){
dto.setResultType(String.valueOf(TaskResultType.Failure));
batchJobNameCache.put(jobName+ "_ossPath", String.valueOf(TaskResultType.Failure));//batchJobNameCache.remove(jobName);
dto.setResultReason("上传压缩包失败");
log.info(" upload zip failure ");
}finally{if(inputStream != null){
inputStream.close();
}
}
}catch(Exception e) {
dto.setResultReason("打包失败");
batchJobNameCache.put(jobName+ "_ossPath", String.valueOf(TaskResultType.Failure));//batchJobNameCache.remove(jobName);
dto.setResultType(String.valueOf(TaskResultType.Failure));
log.info(" zip failure ");
}finally{//删除压缩包
if(zipFile.exists()) {
zipFile.delete();
log.info(jobName+ ".zip has been deleted ! ");
}//更新批次任务为子任务状态(0->1)
tcTransportLogService.updateById(dto);
}
}
}/*** 生成wor并返回相应path*/
class DocMakeTask implements Callable{privateTCPatientsDTO tcPatientsDTO;privateObject template;privateString fileName;private String outPath; //生成报告的临时根目录
privateString templateType;publicDocMakeTask(TCPatientsDTO tcPatientsDTO, String fileName, Object template, String outPath, String templateType) {this.tcPatientsDTO =tcPatientsDTO;this.fileName =fileName;this.template =template;this.outPath =outPath;this.templateType =templateType;
}
@Overridepublic String call() throwsException {//生成的报告的临时目录
String docTempPath = "";//取模板填充数据
Map dataMap = "";docTempPath= outPath + File.separator + "xxx_xxx" +fileName;//生成报告
if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){
FreemarkerUtil.createWordByTemplate((Template) template, docTempPath, dataMap);
}else{
PoiUtil.writeToFileByTemplate((XWPFTemplate) template, docTempPath, dataMap);
}
log.info("word :" +docTempPath);returndocTempPath;
}
}
@GetMapping("archives")
@ApiOperation("单份报告下载")
@LogOperation("单份报告下载")
@ResponseBodypublic Result archives(@RequestParam Map params, HttpServletRequest request) throwsException {//参数判空处理等都略过。。。//根据业务类型取取模板实例(通过oss链接取模板)
String url =wordService.getTemplateUrl(plansCode, downType);if(StringUtils.isEmpty(url)){
log.info("模板链接为空");return new Result().error(202, "请先指定报告模板");
}//取oss模板的后缀(项目支持POI和xml)
String templateType =wordService.getType(url);if(ApiConsts.TEMPLATE_TYPE_XML.equals(templateType)){ //"xml"
templateType=ApiConsts.TEMPLATE_TYPE_XML;
}else{ //"poi"
templateType=ApiConsts.TEMPLATE_TYPE_POI;
}//2、取业务类型对应数据
Map dataMap = new HashMap<>();if(ApiConsts.RESIDENT_HEALTHY.equals(downType)){ //健康档案
dataMap =wordService.getDataMapPoi(plansCode, sn, ApiConsts.RESIDENT_HEALTHY, templateType);
}else if(ApiConsts.RESIDENT_REPORT.equals(downType)){ //体检报告
dataMap =wordService.getDataMapPoi(plansCode, sn, ApiConsts.RESIDENT_REPORT, templateType);
}
String name= MapUtils.getString(dataMap, "name");//oos名称:{downType}/{planscoe}/时间戳/{sn}_{name}_xxx.doc
String ossPathName = downType + "/" + plansCode + "/" + System.currentTimeMillis() + "/" + sn + "_" + name + "_" +fileName;//记录下载痕迹
TCTransportLogDTO dto = null;
String userName=phsUser.getUserName();//上传oss返回的链接
String ossPath = "";try{if(ApiConsts.TEMPLATE_TYPE_POI.equals(templateType)){//临时目录
String outTempPath = wordService.getTempPath() + File.separator + sn + "_" + name +fileName;//多个自定义渲染策略
Configure configures =Configure.createDefault();
configures.customPolicy("urines", new SingleTablePolicy(1, 5));
configures.customPolicy("bloods", new SingleTablePolicy(1, 5));
configures.customPolicy("examines", new SingleTablePolicy(1, 5));
XWPFTemplate template=PoiUtil.getTemplate(url, configures);
template.render(dataMap);
template.writeToFile(outTempPath);
template.close();
ossPath= OSSFactory.build().upload(newFileInputStream(outTempPath), ossPathName);//删除本地临时文件
ZipUtil.delFile(newFile(outTempPath));
}else{
StringWriter out= newStringWriter();
Template template=FreemarkerUtil.getTemplate(url);
template.process(dataMap, out);
ossPath=OSSFactory.build().upload(out.toString().getBytes(StandardCharsets.UTF_8), ossPathName);
}
log.info(sn+ "_" + name + fileName + "生成! doc link:" +ossPath);
dto= newTCTransportLogDTO();
dto.setUserName(userName);
dto.setBusinessType(ApiConsts.TRANSMISSION_DOWNLOAD);//下载
dto.setStatus(1);//有效
dto.setJobType(1);//子任务类型
dto.setResultType(String.valueOf(TaskResultType.Success));//下载成功
dto.setResultReturn(ossPath);//下载存储路径
dto.setResultName(sn + "_" + name + fileName);//下载后文件名称
dto.setCreateDate(newDate());return new Result().success(200, tipStr + "下载完成", ossPath);
}catch(Exception e){
dto.setResultReturn("");//下载存储路径
dto.setResultType(String.valueOf(TaskResultType.Failure));
dto.setResultReason("下载失败");
log.info(sn+ "_" + name + fileName + " 下载失败!");return new Result().success(201, tipStr + "下载失败", "");
}finally{
tcTransportLogService.insert(ConvertUtils.sourceToTarget(dto, TCTransportLogEntity.class));
}
}
@GetMapping("progressList")
@ApiOperation("下载完成记录列表")
@LogOperation("下载完成记录列表")public Result getProgressList(@RequestParam Mapparams, HttpServletRequest request){//取当前用户下的所有子任务(businessType:==1表示上传完毕(待下载),==2表示已下载)
List list =DB.getxxxx(xxx);if(list.isEmpty()){return new Result().success(201, "暂无下载记录~", list);
}return new Result().success(200, "获取下载记录成功", list);
}
@GetMapping("getInTransit")
@ApiOperation("获取打包中列表")
@LogOperation("获取打包中列表")public Result getInTransit(@RequestParam Mapparams, HttpServletRequest request){Integer businessType= MapUtils.getInteger(params, "businessType", ApiConsts.TRANSMISSION_UPLOAD);//获取所有批量任务
List list = tcTransportLogService.getInTransit(phsUser.getUserName(), 0, businessType);if(list.isEmpty()){return new Result().success(201, "暂无下载中任务~", list);
}//实际正在进行的列表
List returnList = new ArrayList<>();//异常任务+已完成任务
List completeList = new ArrayList<>();//下载任务异常列表
List updateList = new ArrayList<>();for(TCTransportLogDTO dto : list){
String jobName=dto.getJobName();//1、缓存中不存在(已完成或者任务没有正常结束两种)
String existJobName = MapUtils.getString(batchJobNameCache, jobName, "");if(StringUtils.isEmpty(existJobName)){if(StringUtils.isEmpty(dto.getResultType())){
dto.setResultType(String.valueOf(TaskResultType.Exception));
dto.setResultReason("任务没有正常结束");
dto.setJobType(1);//任务改为子任务
updateList.add(ConvertUtils.sourceToTarget(dto, TCTransportLogEntity.class));
}
completeList.add(ConvertUtils.sourceToTarget(dto, TCTransportLogEntity.class));continue;
}try{//2、正在进行的工作
int percent =Integer.parseInt(existJobName);
dto.setPercertage(percent);
}catch(Exception e){
e.printStackTrace();
}
returnList.add(dto);
}//处理打包异常
if(!updateList.isEmpty()){
log.info("任务没有正常结束:" +updateList.size());//2、更新数据库状态为异常
tcTransportLogService.updateBatchById(updateList);//检查已完成的列表,删除临时文件
handlerAbnormalTask(completeList);
}if(returnList.isEmpty()){return new Result().success(201, "暂无下载中任务~", returnList);
}return new Result().success(200, "获取下载任务成功", returnList);
}/*** 处理批量下载[下载失败/打包异常]任务
*@paramcompleteList 已完成的列表*/
private void handlerAbnormalTask(ListcompleteList) {
String outTempPath=wordService.getTempPath();for(TCTransportLogEntity entry : completeList){
File zipFile= new File(outTempPath + File.separator + entry.getJobName() + ".zip");
ZipUtil.delFile(zipFile);
File temFileDir= new File(outTempPath + File.separator +entry.getJobName());
ZipUtil.delFile(temFileDir);
}
log.info("delete complete or exception task...");
}/*** 以服务器的最后一个磁盘作为临时目录(返回字符串带文件分隔符)
*@return
*/
privateString getTempPath() {//本地磁盘的根路径
File[] paths =File.listRoots();return paths[paths.length-1].getAbsolutePath();
}
@PostMapping("queryProcess")
@ApiOperation("查询打包进度")
@LogOperation("查询打包进度")public Result queryProcess(@RequestBody Mapparams){
String taskList= MapUtils.getString(params, "jobNames", "");if(StringUtils.isEmpty(taskList)){return new Result().success(201, "下载完成", null);
}
List jobNameList = JSONObject.parseArray(taskList, TCTransportLogDTO.class);
List returnList = new ArrayList<>();//遍历列表,分开已经完成并过期的工作(
for(TCTransportLogDTO dto : jobNameList){
String jobName=dto.getJobName();//1)、缓存中不存在的
String existJobName = MapUtils.getString(batchJobNameCache, jobName, "");if(StringUtils.isEmpty(existJobName)){continue;
}//2)、刷新进度条显示
if(dto.getPercertage() == 100 ){ //打包完成,下载中
dto.setRemark("下载中...");
}else{ //进度 < 100,刷新打包中任务进度
int percent = MapUtils.getIntValue(batchJobNameCache, jobName, 0);
dto.setPercertage(percent);
dto.setRemark("打包中...");
}
returnList.add(dto);
}if(returnList.isEmpty()){return new Result().success(201, "下载完成", returnList);
}return new Result().success(200, "刷新打包进度条", returnList);
}
@GetMapping("monitorPackage")
@ApiOperation("监听打包")
@LogOperation("监听打包")public Result monitorPackage(@RequestParam Mapparams, HttpServletResponse response){
String jobName= MapUtils.getString(params, "jobName", "");if(StringUtils.isEmpty(jobName)){
log.info("[ monitorPackage ] jobName parameter is missing");return new Result().error(202, "jobName parameter is missing");
}
String jobNameExist= MapUtils.getString(batchJobNameCache, jobName, "");if(StringUtils.isEmpty(jobNameExist)){
log.info("[ monitorPackage ] [" + jobName + "] is not found");return new Result().error(202, "[" + jobName + "] is not found");
}//进度
int percentage =MapUtils.getIntValue(batchJobNameCache, jobName);//打包完成后,判断oss链接
String ossPath = MapUtils.getString(batchJobNameCache, jobName + "_ossPath");if(StringUtils.isEmpty(ossPath)){
log.info("zip being packaged");return new Result().success(201, "zip being packaged", percentage);
}else{//2、从缓存中剔除
batchJobNameCache.remove(jobName);
batchJobNameCache.remove(jobName+ "_ossPath");
log.info(" remove from batchJobNameCache cache ");if(String.valueOf(TaskResultType.Failure).equals(ossPath)){//打包失败/上传失败==下载失败
log.info(percentage==100 ? "上传失败" : "打包失败");return new Result().success(202, percentage==100 ? "上传失败" : "打包失败", ossPath);
}//下载成功
log.info("package is complete, ready to download ");return new Result().success(200, "package is complete, ready to download ", ossPath);
}
}
@GetMapping("queryDetail")
@ApiOperation("查询详情")
@LogOperation("查询详情")public String queryDetail(@RequestParam("jobName") String jobName){
List> taskDetail =pendingJobPool.getTaskDetail(jobName);if(!taskDetail.isEmpty()){returntaskDetail.toString();
}return null;
}
@GetMapping("clearMark")
@ApiOperation("清除下载记录")
@LogOperation("清除下载记录")public Result clearMark(@RequestParam Mapparams, HttpServletRequest request){
String clearIds= MapUtils.getString(params, "clearIds", "");if(StringUtils.isEmpty(clearIds)) {
log.info("传输完成记录主键为空");return new Result().error(201, "丢失需要清除的记录主键信息");
}
List listIds = Arrays.asList(clearIds.split(",")).stream().map(s ->Long.parseLong(s.trim())).collect(Collectors.toList());int row =tcTransportLogService.clearByIds(listIds);if(row ==listIds.size()){
log.info("清除传输记录成功");return new Result().success(200, "清除成功", row);
}
log.info("清除传输记录失败");return new Result().success(202, "清除失败", row);
}
}