综合浏览数据表自定义查询自定义导出功能

master
guoxin 1 year ago
parent 3f2007cf9f
commit 47f9f95818
  1. 53
      shandan-browser/src/main/java/com/keyware/shandan/browser/controller/SearchController.java
  2. 8
      shandan-browser/src/main/java/com/keyware/shandan/browser/service/MetadataDataService.java
  3. 46
      shandan-browser/src/main/java/com/keyware/shandan/browser/service/impl/MetadataDataServiceImpl.java
  4. 42
      shandan-browser/src/main/resources/static/js/browser.js
  5. 12
      shandan-browser/src/main/resources/static/js/meta_search.js
  6. 43
      shandan-common/src/main/resources/static/js/common/common.js
  7. 153
      shandan-system/src/main/java/com/keyware/shandan/bianmu/export/DirectoryExport.java
  8. 47
      shandan-system/src/main/java/com/keyware/shandan/bianmu/export/ExportCache.java
  9. 90
      shandan-system/src/main/java/com/keyware/shandan/bianmu/export/ExportProgress.java
  10. 119
      shandan-system/src/main/java/com/keyware/shandan/bianmu/export/MetaTableExport.java
  11. 15
      shandan-system/src/main/java/com/keyware/shandan/bianmu/utils/MetadataUtils.java
  12. 12
      shandan-system/src/main/java/com/keyware/shandan/datasource/mapper/DynamicDatasourceMapper.java

@ -7,16 +7,24 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.keyware.shandan.bianmu.entity.DirectoryResource; import com.keyware.shandan.bianmu.entity.DirectoryResource;
import com.keyware.shandan.bianmu.entity.MetadataBasicVo; import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.bianmu.export.DirectoryExport;
import com.keyware.shandan.bianmu.export.ExportCache;
import com.keyware.shandan.bianmu.export.ExportProgress;
import com.keyware.shandan.bianmu.export.MetaTableExport;
import com.keyware.shandan.bianmu.service.MetadataService; import com.keyware.shandan.bianmu.service.MetadataService;
import com.keyware.shandan.browser.entity.FullSearchParam; import com.keyware.shandan.browser.entity.FullSearchParam;
import com.keyware.shandan.browser.entity.PageVo; import com.keyware.shandan.browser.entity.PageVo;
import com.keyware.shandan.browser.entity.SearchConditionVo; import com.keyware.shandan.browser.entity.SearchConditionVo;
import com.keyware.shandan.browser.service.*; import com.keyware.shandan.browser.service.FileSearchService;
import com.keyware.shandan.browser.service.MetadataDataService;
import com.keyware.shandan.browser.service.MetadataSearchService;
import com.keyware.shandan.browser.service.SearchService;
import com.keyware.shandan.common.entity.Result; import com.keyware.shandan.common.entity.Result;
import com.keyware.shandan.common.util.FileDownload; import com.keyware.shandan.common.util.FileDownload;
import com.keyware.shandan.frame.annotation.AppLog; import com.keyware.shandan.frame.annotation.AppLog;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -24,6 +32,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 组合搜索前端控制器 * 组合搜索前端控制器
@ -202,19 +211,34 @@ public class SearchController {
*/ */
@PostMapping("/export/directory/{directoryId}") @PostMapping("/export/directory/{directoryId}")
public Result<Object> exportQuery(@PathVariable String directoryId, SearchConditionVo condition, Authentication auth) { public Result<Object> exportQuery(@PathVariable String directoryId, SearchConditionVo condition, Authentication auth) {
Assert.notNull(auth, "用户信息认证失败");
String userId = auth.getPrincipal().toString(); String userId = auth.getPrincipal().toString();
ExportComponent export = null;
// 这个逻辑只允许同一个用户同时只能有一个导出任务 // 这个逻辑只允许同一个用户同时只能有一个导出任务
if (ExportComponent.getCache(userId) != null) { ExportProgress export = ExportCache.getCache(userId, null);
export = ExportComponent.getCache(userId).values().stream().findFirst().orElse(null);
}
if (export == null) { if (export == null) {
export = new ExportComponent(userId);
// 查询数据 // 查询数据
List<DirectoryResource> list = metadataSearchService.searchAllListByCondition(directoryId, condition); List<DirectoryResource> list = metadataSearchService.searchAllListByCondition(directoryId, condition);
export.setData(list); export = new DirectoryExport(userId, list);
// 打包数据 ExportCache.startExport(export);
export.start(); }
return Result.of(export.getProgress(), !export.isError());
}
@PostMapping("/export/metadata/{metaId}")
public Result<Object> exportMetadata(@PathVariable String metaId, SearchConditionVo condition, Authentication auth) {
Assert.notNull(auth, "用户信息认证失败");
MetadataBasicVo metadata = metadataService.getById(metaId);
Assert.notNull(metadata, "数据资源表不存在");
String userId = auth.getPrincipal().toString();
MetaTableExport export = ExportCache.getCache(userId, null);
if (export == null) {
int count = metadataDataService.count(metadata, condition);
MetaTableExport newExport = new MetaTableExport(userId, metadata);
newExport.queryData((o) -> metadataDataService.queryDataByHandler(metadata, condition, newExport));
newExport.setDataCount(count);
export = newExport;
ExportCache.startExport(export);
} }
return Result.of(export.getProgress()); return Result.of(export.getProgress());
@ -229,11 +253,14 @@ public class SearchController {
@GetMapping("/export/status/{exportId}") @GetMapping("/export/status/{exportId}")
public Result<Object> exportStatus(@PathVariable String exportId, Authentication auth) { public Result<Object> exportStatus(@PathVariable String exportId, Authentication auth) {
String userId = auth.getPrincipal().toString(); String userId = auth.getPrincipal().toString();
ExportComponent export = ExportComponent.getCache(userId, exportId); ExportProgress export = ExportCache.getCache(userId, exportId);
if (export == null) { if (export == null) {
return Result.of(null, false, "导出任务不存在"); return Result.of(null, false, "导出任务不存在");
} }
return Result.of(export.getProgress()); if (export.isError()) {
ExportCache.popCache(userId, exportId);
}
return Result.of(export.getProgress(), !export.isError());
} }
/** /**
@ -246,11 +273,11 @@ public class SearchController {
@GetMapping("/export/download/{exportId}") @GetMapping("/export/download/{exportId}")
public String exportDownload(HttpServletResponse response, @PathVariable String exportId, Authentication auth) { public String exportDownload(HttpServletResponse response, @PathVariable String exportId, Authentication auth) {
String userId = auth.getPrincipal().toString(); String userId = auth.getPrincipal().toString();
ExportComponent export = ExportComponent.popCache(userId, exportId); ExportProgress export = ExportCache.popCache(userId, exportId);
if (export == null) { if (export == null) {
return "错误:导出任务不存在"; return "错误:导出任务不存在";
} }
String path = export.getExportPath() + ".zip"; String path = export.getDownloadPath();
String downloadName = FileUtil.getName(path); String downloadName = FileUtil.getName(path);
File file = new File(path); File file = new File(path);
return FileDownload.download(response, file, downloadName, true); return FileDownload.download(response, file, downloadName, true);

@ -3,6 +3,10 @@ package com.keyware.shandan.browser.service;
import com.keyware.shandan.bianmu.entity.MetadataBasicVo; import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.browser.entity.PageVo; import com.keyware.shandan.browser.entity.PageVo;
import com.keyware.shandan.browser.entity.SearchConditionVo; import com.keyware.shandan.browser.entity.SearchConditionVo;
import org.apache.ibatis.session.ResultHandler;
import java.util.HashMap;
import java.util.Map;
/** /**
* 资源数据服务类 * 资源数据服务类
@ -12,4 +16,8 @@ public interface MetadataDataService {
PageVo queryData(MetadataBasicVo metadata, SearchConditionVo condition); PageVo queryData(MetadataBasicVo metadata, SearchConditionVo condition);
String getQuerySql(MetadataBasicVo metadata, SearchConditionVo condition); String getQuerySql(MetadataBasicVo metadata, SearchConditionVo condition);
int count(MetadataBasicVo metadata, SearchConditionVo condition);
void queryDataByHandler(MetadataBasicVo metadata, SearchConditionVo condition, ResultHandler<HashMap<String, Object>> handler);
} }

@ -10,6 +10,7 @@ import com.keyware.shandan.browser.service.MetadataDataService;
import com.keyware.shandan.datasource.entity.DataSourceVo; import com.keyware.shandan.datasource.entity.DataSourceVo;
import com.keyware.shandan.datasource.mapper.DynamicDatasourceMapper; import com.keyware.shandan.datasource.mapper.DynamicDatasourceMapper;
import com.keyware.shandan.datasource.service.DataSourceService; import com.keyware.shandan.datasource.service.DataSourceService;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -17,6 +18,8 @@ import java.sql.Blob;
import java.sql.Clob; import java.sql.Clob;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -56,6 +59,32 @@ public class MetadataDataServiceImpl implements MetadataDataService {
return sql; return sql;
} }
public String getCountSql(MetadataBasicVo metadata, SearchConditionVo condition) {
DataSourceVo dataSource = dataSourceService.getById(metadata.getDataSourceId());
if (dataSource == null) {
throw new RuntimeException("系统没有找到数据源配置");
}
String sql = MetadataUtils.generateSql(metadata, dataSource, true);
sql += condition.getWhereSql(false);
return sql;
}
@Override
public int count(MetadataBasicVo metadata, SearchConditionVo condition) {
String sql = getCountSql(metadata,condition);
return dynamicDatasourceMapper.count(sql);
}
@Override
public void queryDataByHandler(MetadataBasicVo metadata, SearchConditionVo condition, ResultHandler<HashMap<String, Object>> handler) {
String sql = getQuerySql(metadata, condition);
sql += condition.getOrderBySql();
dynamicDatasourceMapper.listByHandler(sql, (context) -> {
context.getResultObject().entrySet().forEach(this::convertBigTextField);
handler.handleResult(context);
});
}
/** /**
* 动态查询分页数据 * 动态查询分页数据
* *
@ -67,7 +96,20 @@ public class MetadataDataServiceImpl implements MetadataDataService {
private PageVo dynamicQueryPage(SearchConditionVo condition, String sql) { private PageVo dynamicQueryPage(SearchConditionVo condition, String sql) {
Page<HashMap<String, Object>> page = dynamicDatasourceMapper.page(new Page<>(condition.getPage(), condition.getSize()), sql); Page<HashMap<String, Object>> page = dynamicDatasourceMapper.page(new Page<>(condition.getPage(), condition.getSize()), sql);
// 处理Clob类型 // 处理Clob类型
page.setRecords(page.getRecords().stream().peek(data -> data.entrySet().stream().peek(entry -> { List<HashMap<String, Object>> records = page.getRecords()
.stream()
.peek(data -> data.entrySet().forEach(this::convertBigTextField))
.collect(Collectors.toList());
page.setRecords(records);
return PageVo.pageConvert(page);
}
/**
* 处理Blob和Clob大字段类型
*
* @param entry
*/
private void convertBigTextField(Map.Entry<String, Object> entry) {
if (entry.getValue() instanceof Clob) { if (entry.getValue() instanceof Clob) {
Clob clob = (Clob) entry.getValue(); Clob clob = (Clob) entry.getValue();
try { try {
@ -78,7 +120,5 @@ public class MetadataDataServiceImpl implements MetadataDataService {
} else if (entry.getValue() instanceof Blob) { } else if (entry.getValue() instanceof Blob) {
entry.setValue("(Blob)"); entry.setValue("(Blob)");
} }
}).collect(Collectors.toSet())).collect(Collectors.toList()));
return PageVo.pageConvert(page);
} }
} }

@ -421,47 +421,7 @@ layui.use(['layer', 'listPage', 'globalTree', 'gtable', 'form', 'element', 'drop
} }
function exportDirectory(id, params) { function exportDirectory(id, params) {
let layerIndex; Util.exportResource(`/search/export/directory/${id}`, params)
showExportMsgLayer('正在查询需要导出的数据');
Util.post(`/search/export/directory/${id}`, params, false).then(({flag, data, msg}) => {
if (flag) {
showExportMsgLayer(data.msg);
queryProgress(data.exportId);
} else {
showExportMsgLayer('数据查询失败,' + msg);
}
})
function queryProgress(exportId) {
setTimeout(function () {
Util.get(`/search/export/status/${exportId}`, {}, false).then(({flag, data, msg}) => {
if (flag) {
showExportMsgLayer(data.msg);
if (data.isDone) {
layer.close(layerIndex);
parent.window.open(`${ctx}/search/export/download/${exportId}`, '_blank')
} else {
queryProgress(exportId);
}
} else {
showExportMsgLayer('数据查询失败,' + msg);
}
});
}, 1000);
}
function showExportMsgLayer(msg) {
layerIndex && layer.close(layerIndex);
layerIndex = layer.msg(msg, {
icon: 16,
shade: 0.01,
anim: -1,
isOutAnim: false,
time: 1000 * 60 * 30,
closeBtn: 1
});
}
} }
/** /**

@ -37,10 +37,14 @@ layui.use(['dropdown', 'gtable', 'laydate', 'form'], function () {
limit: 20, limit: 20,
toolbar: '#table-title', toolbar: '#table-title',
request: {pageName: 'page', limitName: 'size'}, request: {pageName: 'page', limitName: 'size'},
defaultToolbar: ['filter', 'print', 'exports', { defaultToolbar: ['filter', {
title: '导出数据',
layEvent: 'exports',
icon: 'layui-icon-export'
}, {
title: '统计报表', title: '统计报表',
layEvent: 'tong-ji', layEvent: 'tong-ji',
icon: 'layui-icon-chart' //图标类名 icon: 'layui-icon-chart'
}] }]
}) })
// 排序事件回调 // 排序事件回调
@ -53,8 +57,10 @@ layui.use(['dropdown', 'gtable', 'laydate', 'form'], function () {
}); });
// 统计报表自定义 // 统计报表自定义
result_table.on('toolbar', function ({event}) { result_table.on('toolbar', function ({event}) {
if (event == 'tong-ji') { if (event === 'tong-ji') {
openStatisticalReport() openStatisticalReport()
}else if(event === 'exports'){
Util.exportResource(`/search/export/metadata/${metadata.id}`, where);
} }
}); });
}); });

@ -546,6 +546,49 @@ commonUtil = {
return Math.round(size * 100) / 100 + initUnit; return Math.round(size * 100) / 100 + initUnit;
} }
}, },
exportResource: (url, params)=>{
let layerIndex;
showExportMsgLayer('开始准备数据');
Util.post(url, params, false).then(({flag, data, msg}) => {
if (flag) {
showExportMsgLayer(data.msg);
queryProgress(data.exportId);
} else {
showExportMsgLayer('数据查询失败,' + msg);
}
})
function queryProgress(exportId) {
setTimeout(function () {
Util.get(`/search/export/status/${exportId}`, {}, false).then(({flag, data, msg}) => {
if (flag) {
showExportMsgLayer(data.msg);
if (data.isDone) {
layer.close(layerIndex);
parent.window.open(`${ctx}/search/export/download/${exportId}`, '_blank')
} else {
queryProgress(exportId);
}
} else {
showExportMsgLayer('数据查询失败,' + msg);
}
});
}, 1000);
}
function showExportMsgLayer(msg) {
layerIndex && layer.close(layerIndex);
layerIndex = layer.msg(msg, {
icon: 16,
shade: 0.01,
anim: -1,
isOutAnim: false,
time: 1000 * 60 * 30,
closeBtn: 1
});
}
}
}; };
const Util = commonUtil; const Util = commonUtil;

@ -1,4 +1,4 @@
package com.keyware.shandan.browser.service; package com.keyware.shandan.bianmu.export;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
@ -14,99 +14,54 @@ import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.bianmu.service.MetadataService; import com.keyware.shandan.bianmu.service.MetadataService;
import com.keyware.shandan.common.util.StreamUtil; import com.keyware.shandan.common.util.StreamUtil;
import com.keyware.shandan.common.util.StringUtils; import com.keyware.shandan.common.util.StringUtils;
import com.keyware.shandan.common.util.UUIDUtil;
import com.keyware.shandan.frame.config.component.AppContext; import com.keyware.shandan.frame.config.component.AppContext;
import com.keyware.shandan.frame.properties.CustomProperties;
import com.keyware.shandan.system.entity.SysFile; import com.keyware.shandan.system.entity.SysFile;
import com.keyware.shandan.system.service.SysFileService; import com.keyware.shandan.system.service.SysFileService;
import com.keyware.shandan.system.utils.SysSettingUtil; import com.keyware.shandan.system.utils.SysSettingUtil;
import lombok.Data;
import lombok.Getter;
import java.io.File; import java.io.File;
import java.io.Serializable; import java.util.ArrayList;
import java.util.*; import java.util.Date;
import java.util.concurrent.ConcurrentHashMap; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class ExportComponent extends Thread { public class DirectoryExport extends ExportProgress {
@Getter private final List<DirectoryResource> dataList;
private final String exportId = UUIDUtil.getUUID();
private final String fileName;
private List<DirectoryResource> dataList;
private final CustomProperties customProperties;
private final SysFileService fileService; private final SysFileService fileService;
private final MetadataService metadataService; private final MetadataService metadataService;
private final String outputDir = "目录导出_" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
private final String outputPath = tempDir + File.separator + outputDir;
private final String zipName = outputPath + ".zip";
@Getter public DirectoryExport(String userId, List<DirectoryResource> dataList) {
private final Progress progress; super(userId);
this.dataList = dataList;
private final static ConcurrentHashMap<String, Map<String, ExportComponent>> cache = new ConcurrentHashMap<>();
public ExportComponent(String userId) {
this.customProperties = AppContext.getContext().getBean(CustomProperties.class);
this.fileService = AppContext.getContext().getBean(SysFileService.class); this.fileService = AppContext.getContext().getBean(SysFileService.class);
this.metadataService = AppContext.getContext().getBean(MetadataService.class); this.metadataService = AppContext.getContext().getBean(MetadataService.class);
fileName = "目录导出_" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
this.progress = new Progress(this.exportId);
setCache(userId);
}
public void setCache(String userId) {
Map<String, ExportComponent> userCache = cache.get(userId);
if (userCache == null) {
userCache = new HashMap<>();
}
userCache.put(this.exportId, this);
cache.put(userId, userCache);
}
static public Map<String, ExportComponent> getCache(String userId) {
return cache.get(userId);
}
static public ExportComponent getCache(String userId, String exportId) {
Map<String, ExportComponent> userCache = getCache(userId);
if (userCache == null) {
return null;
} else {
return userCache.get(exportId);
}
} }
public static ExportComponent popCache(String userId, String exportId) {
Map<String, ExportComponent> userCache = getCache(userId);
if (userCache == null) {
return null;
} else {
return userCache.remove(exportId);
}
}
/**
* 开始打包
*/
@Override @Override
public void run() { public void run() {
String path = getExportPath();
try { try {
this.progress.setTitle("正在生成临时数据文件"); this.setTitle("正在生成临时数据文件");
dataList.forEach(resource -> { dataList.forEach(resource -> {
if ("file".equals(resource.getResourceType())) { if ("file".equals(resource.getResourceType())) {
exportFile(resource); exportFile(resource);
} else { } else {
exportMetadata(resource); exportMetadata(resource);
} }
this.progress.offsetIncrease(); this.autoAddStep();
}); });
this.progress.setTitle("正在打包数据文件"); this.setTitle("正在打包数据文件");
ZipUtil.zip(path, path + ".zip", true); ZipUtil.zip(outputPath, zipName, true);
this.progress.setTitle("数据包准备完毕,开始下载"); this.autoAddStep();
this.progress.offsetIncrease(); this.setDone();
this.setTitle("数据包准备完毕,开始下载");
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("导出数据在打包时出错", e); throw new RuntimeException("导出数据在打包时出错", e);
} finally { } finally {
delete(path); delete(outputPath);
} }
} }
@ -149,7 +104,6 @@ public class ExportComponent extends Thread {
} catch (Exception e) { } catch (Exception e) {
colsArray = new JSONArray(); colsArray = new JSONArray();
JSONObject defaultJson = new JSONObject(); JSONObject defaultJson = new JSONObject();
// defaultJson.put("columnName", "");
defaultJson.put("comment", "数据查询失败,无法连接到指定的数据库"); defaultJson.put("comment", "数据查询失败,无法连接到指定的数据库");
colsArray.add(defaultJson); colsArray.add(defaultJson);
datas = new ArrayList<>(); datas = new ArrayList<>();
@ -197,21 +151,6 @@ public class ExportComponent extends Thread {
writer.close(); writer.close();
} }
/**
* 设置需要导出的数据
*
* @param dataList 数据
*/
public void setData(List<DirectoryResource> dataList) {
this.dataList = dataList;
this.progress.setFileTotal(dataList.size());
this.progress.offsetIncrease();
}
public String getExportPath() {
return customProperties.getFileStorage().getTempPath() + "/" + fileName;
}
/** /**
* 根据资源路径创建目录 * 根据资源路径创建目录
* *
@ -219,7 +158,7 @@ public class ExportComponent extends Thread {
* @return 资源的的绝对路径 * @return 资源的的绝对路径
*/ */
private String createResourceDir(String resourcePath) { private String createResourceDir(String resourcePath) {
String path = getExportPath() + "/" + resourcePath; String path = outputPath + File.separator + resourcePath;
File file = new File(path); File file = new File(path);
File parent = file.getParentFile(); File parent = file.getParentFile();
if (!parent.exists()) { if (!parent.exists()) {
@ -235,49 +174,13 @@ public class ExportComponent extends Thread {
} }
} }
@Data @Override
static class Progress implements Serializable { public long getStepTotal() {
return dataList.size() + 1;
private String exportId;
private String title;
private Integer fileTotal;
private Integer offset = 0;
public Progress(String exportId) {
this.exportId = exportId;
this.title = "正在查询需要导出的数据";
}
public void setFileTotal(Integer fileTotal) {
// 加2是因为需要增加一个查询步骤和一个打包的步骤
this.fileTotal = fileTotal + 2;
}
public void offsetIncrease() {
this.offset++;
}
/**
* 获取百分比
*
* @return
*/
public int getPercentage() {
return Math.round(offset / Float.valueOf(fileTotal) * 100);
}
/**
* 判断是否完成
*
* @return
*/
public boolean getIsDone() {
return Objects.equals(offset, fileTotal);
} }
public String getMsg() { @Override
return "(" + getPercentage() + "%) " + this.title; public String getDownloadPath() {
} return zipName;
} }
} }

@ -0,0 +1,47 @@
package com.keyware.shandan.bianmu.export;
import com.keyware.shandan.common.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ExportCache {
private final static ConcurrentHashMap<String, Map<String, ExportProgress>> cache = new ConcurrentHashMap<>();
public static void startExport(ExportProgress progress) {
Map<String, ExportProgress> userCache = cache.get(progress.getUserId());
if (userCache == null) {
userCache = new HashMap<>();
}
userCache.put(progress.getExportId(), progress);
cache.put(progress.getUserId(), userCache);
new Thread(progress).start();
}
public static <T extends ExportProgress> T getCache(String userId, String exportId) {
Map<String, ExportProgress> userCache = cache.get(userId);
if (userCache == null) {
return null;
}
if (StringUtils.hasText(exportId)) {
return (T) userCache.get(exportId);
}
Optional<ExportProgress> optional = userCache.values().stream().findFirst();
if (optional.isPresent()) {
return (T) optional.get();
}
return null;
}
public static <T extends ExportProgress> T popCache(String userId, String exportId) {
Map<String, ExportProgress> userCache = cache.get(userId);
if (userCache == null) {
return null;
} else {
ExportProgress progress = userCache.remove(exportId);
return (T) progress;
}
}
}

@ -0,0 +1,90 @@
package com.keyware.shandan.bianmu.export;
import cn.hutool.core.io.FileUtil;
import com.keyware.shandan.common.util.UUIDUtil;
import com.keyware.shandan.frame.config.component.AppContext;
import com.keyware.shandan.frame.properties.CustomProperties;
import lombok.Data;
import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@Data
public abstract class ExportProgress implements Runnable, Serializable {
private String exportId = UUIDUtil.getUUID();
private String userId;
private String title;
private Integer currentStep = 0;
protected final String tempDir;
protected boolean error = false;
public ExportProgress(String userId) {
this.userId = userId;
this.title = "正在查询需要导出的数据";
CustomProperties customProperties = AppContext.getContext().getBean(CustomProperties.class);
tempDir = customProperties.getFileStorage().getTempPath();
}
public abstract void run();
/**
* 进度总步数
*/
public abstract long getStepTotal();
public abstract String getDownloadPath();
/**
* 偏移量自增
*/
public void autoAddStep() {
this.currentStep++;
}
/**
* 获取百分比
*
* @return
*/
public int getPercentage() {
return Math.round(currentStep / (float) getStepTotal() * 100);
}
/**
* 判断是否完成
*
* @return
*/
public boolean getIsDone() {
return currentStep == getStepTotal();
}
protected void setDone() {
currentStep = getCurrentStep();
}
public String getMsg() {
return "(" + getPercentage() + "%) " + this.title;
}
public Map<String, Object> getProgress() {
Map<String, Object> map = new HashMap<>();
map.put("exportId", exportId);
map.put("title", title);
map.put("msg", getMsg());
map.put("isDone", getIsDone());
map.put("isError", error);
return map;
}
public void clean() {
File file = new File(getDownloadPath());
if (file.exists()) {
FileUtil.del(file);
}
}
}

@ -0,0 +1,119 @@
package com.keyware.shandan.bianmu.export;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.keyware.shandan.bianmu.entity.MetadataBasicVo;
import com.keyware.shandan.bianmu.service.MetadataService;
import com.keyware.shandan.common.util.StreamUtil;
import com.keyware.shandan.common.util.StringUtils;
import com.keyware.shandan.frame.config.component.AppContext;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class MetaTableExport extends ExportProgress implements ResultHandler<HashMap<String, Object>> {
private final MetadataBasicVo metadata;
private Consumer<Object> queryFunc;
private final String fileName;
private JSONArray colsArray;
private final MetadataService metadataService;
// 所有行的集合
private List<List<String>> rowList = new ArrayList<>();
// 字段列名集合
private List<String> colNameList = new ArrayList<>();
private int dataTotal;
public MetaTableExport(String userId, MetadataBasicVo metadata) {
super(userId);
this.metadataService = AppContext.getContext().getBean(MetadataService.class);
this.metadata = metadata;
String name = metadata.getMetadataComment();
if (!StringUtils.hasText(name)) {
name = metadata.getMetadataName();
}
fileName = tempDir + File.separator + name + ".xlsx";
}
@Override
public void run() {
this.setTitle("正在读取数据表信息");
colsArray = metadataService.getColumns(metadata.getId());
// 添加第一行,字段列名注释集合,当做表格的第一行
rowList.add(colsArray.stream().map((json) -> {
JSONObject field = (JSONObject) json;
String comment = field.getString("comment");
String colName = field.getString("columnName");
colNameList.add(colName);
return colName + (StringUtils.hasText(comment) ? "[" + comment + "]" : "");
}).collect(Collectors.toList()));
this.setTitle("正在读取数据到临时文件");
// 执行数据查询
try {
queryFunc.accept(null);
File file = new File(fileName);
if(file.exists()){
file.delete();
}
List<List<String>> rows = CollUtil.newArrayList(rowList);
//通过工具类创建writer
ExcelWriter writer = ExcelUtil.getWriter(file);
//一次性写出内容
writer.write(rows);
//关闭writer,释放内存
writer.close();
this.setTitle("数据文件准备完毕,开始下载");
this.setDone();
} catch (Exception e) {
this.error = true;
this.setTitle("准备数据时出现异常");
this.clean();
}
}
@Override
public long getStepTotal() {
return this.dataTotal;
}
@Override
public String getDownloadPath() {
return this.fileName;
}
@Override
public void handleResult(ResultContext<? extends HashMap<String, Object>> context) {
Map<String, Object> data = context.getResultObject();
// 遍历字段列名,获取对应数据并拼接成列的集合
List<String> cells = colNameList.stream().map(col -> {
Object value = data.get(col);
value = value == null ? "" : value;
return String.valueOf(value);
}).collect(Collectors.toList());
rowList.add(cells);
this.autoAddStep();
}
public void queryData(Consumer<Object> query) {
this.queryFunc = query;
}
public void setDataCount(int count) {
this.dataTotal = count;
}
}

@ -24,6 +24,10 @@ import java.util.stream.Collectors;
public class MetadataUtils { public class MetadataUtils {
public static String generateSql(MetadataBasicVo metadata, DataSourceVo dataSource) { public static String generateSql(MetadataBasicVo metadata, DataSourceVo dataSource) {
return generateSql(metadata, dataSource, false);
}
public static String generateSql(MetadataBasicVo metadata, DataSourceVo dataSource, boolean isCountSql) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
String schema = dataSource.getJdbcSchema(); String schema = dataSource.getJdbcSchema();
@ -38,7 +42,7 @@ public class MetadataUtils {
if (optional.isPresent()) { if (optional.isPresent()) {
MetadataDetailsVo masterTable = optional.get(); MetadataDetailsVo masterTable = optional.get();
JSONArray masterColumns = getColumns(masterTable); JSONArray masterColumns = getColumns(masterTable);
if (masterColumns.size() > 0) { if (!masterColumns.isEmpty()) {
StringBuilder tableNameBuilder = new StringBuilder(); StringBuilder tableNameBuilder = new StringBuilder();
StringBuilder columnsBuilder = new StringBuilder(); StringBuilder columnsBuilder = new StringBuilder();
StringBuilder wheresBuilder = new StringBuilder(); StringBuilder wheresBuilder = new StringBuilder();
@ -47,12 +51,14 @@ public class MetadataUtils {
// 主表 // 主表
String master_table = masterTable.getTableName(); String master_table = masterTable.getTableName();
if (!isCountSql) {
// 主表列 // 主表列
masterColumns.forEach(o -> { masterColumns.forEach(o -> {
JSONObject obj = (JSONObject) o; JSONObject obj = (JSONObject) o;
columnsBuilder.append(" \"").append(master_table).append("\".\"").append(obj.getString("columnName")).append("\","); columnsBuilder.append(" \"").append(master_table).append("\".\"").append(obj.getString("columnName")).append("\",");
columnTemp.add(obj.getString("columnName")); columnTemp.add(obj.getString("columnName"));
}); });
}
// 主表表名 // 主表表名
tableNameBuilder.append(schema).append("\"").append(master_table).append("\" as \"").append(master_table).append("\""); tableNameBuilder.append(schema).append("\"").append(master_table).append("\" as \"").append(master_table).append("\"");
@ -66,18 +72,23 @@ public class MetadataUtils {
MetadataDetailsVo table = tables.get(i); MetadataDetailsVo table = tables.get(i);
tableNameBuilder.append(", ").append(schema).append("\"").append(table.getTableName()).append("\" as \"").append(table.getTableName()).append("\""); tableNameBuilder.append(", ").append(schema).append("\"").append(table.getTableName()).append("\" as \"").append(table.getTableName()).append("\"");
if (!isCountSql) {
getColumns(table).forEach(obj -> { getColumns(table).forEach(obj -> {
JSONObject col = (JSONObject) obj; JSONObject col = (JSONObject) obj;
if (!columnTemp.contains(col.getString("columnName"))) { if (!columnTemp.contains(col.getString("columnName"))) {
columnsBuilder.append("\"").append(table.getTableName()).append("\".\"").append(col.getString("columnName")).append("\","); columnsBuilder.append("\"").append(table.getTableName()).append("\".\"").append(col.getString("columnName")).append("\",");
} }
}); });
}
// where 条件 // where 条件
appendWhere(tables, table, wheresBuilder, "\"" + table.getTableName() + "\""); appendWhere(tables, table, wheresBuilder, "\"" + table.getTableName() + "\"");
} }
builder.append("select ").append(columnsBuilder.substring(0, columnsBuilder.toString().length() - 1)) String select = isCountSql ? " count(1) " : columnsBuilder.substring(0, columnsBuilder.toString().length() - 1);
builder.append("select ").append(select)
.append(" from ").append(tableNameBuilder) .append(" from ").append(tableNameBuilder)
.append(" where 1=1 ").append(wheresBuilder); .append(" where 1=1 ").append(wheresBuilder);

@ -1,8 +1,13 @@
package com.keyware.shandan.datasource.mapper; package com.keyware.shandan.datasource.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.keyware.shandan.bianmu.entity.DirectoryVo;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.ResultType;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.session.ResultHandler;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -25,4 +30,11 @@ public interface DynamicDatasourceMapper {
@Select("${sql}") @Select("${sql}")
Page<HashMap<String, Object>> page(Page page, String sql); Page<HashMap<String, Object>> page(Page page, String sql);
@Select("${sql}")
@ResultType(HashMap.class)
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
void listByHandler(String sql, ResultHandler<HashMap<String, Object>> handler);
@Select("${sql}")
int count(String sql);
} }