瀏覽代碼

增加批量导出功能

William 2 年之前
父節點
當前提交
07d9ac9850

+ 3 - 0
src/main/java/org/springblade/flow/base/ActionResult.java

@@ -102,4 +102,7 @@ public class ActionResult<T> {
 		return new ActionResult<T>(400, message, data);
 	}
 
+	public static <T> ActionResult<T> status(boolean success, T data) {
+		return new ActionResult<T>(success ? 200 : 400, success ? "操作成功" : "操作失败", data);
+	}
 }

+ 221 - 0
src/main/java/org/springblade/flow/datalog/common/ExportService.java

@@ -0,0 +1,221 @@
+package org.springblade.flow.datalog.common;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.springblade.bi.client.model.PageModel;
+import org.springblade.flow.datalog.enums.DataExportTaskStatus;
+import org.springblade.flow.datalog.model.DataExportTask;
+import org.springblade.flow.datalog.service.DataExportTaskService;
+import org.springblade.flow.util.JsonUtil;
+import org.springblade.flow.visual.base.entity.VisualdevEntity;
+import org.springblade.flow.visual.base.service.VisualdevService;
+import org.springblade.flow.visual.onlinedev.common.formdata.FormDataWithTableService;
+import org.springblade.flow.visual.onlinedev.common.relation.RelationField;
+import org.springblade.flow.visual.onlinedev.model.PaginationModel;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.util.FileUtils;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 处理导出表单数据的任务
+ * 
+ * @author William
+ * @date 2022年12月2日 下午1:43:17
+ */
+@Slf4j
+@Service
+public class ExportService {
+	private final static int PAGE_SIZE = 5000;
+
+	@Value("${blade.file.formDir}")
+	private String formDir;
+
+	@Autowired
+	private DataExportTaskService dataExportTaskService;
+
+	@Autowired
+	private FormDataWithTableService formDataWithTableService;
+
+	@Autowired
+	private VisualdevService visualdevService;
+
+	final ExecutorService threadPool = new ThreadPoolExecutor(1, 2, 5, TimeUnit.MINUTES,
+			// 有界队列5000,超过队列长度,无异常抛弃
+			new ArrayBlockingQueue<>(5000), new ThreadFactory() {
+				@Override
+				public Thread newThread(Runnable r) {
+					Thread thread = new Thread(r);
+					thread.setDaemon(false);
+					thread.setName("ExportFormData-Pool-");
+					return thread;
+				}
+			}, new ThreadPoolExecutor.DiscardPolicy());
+
+	public void excute(DataExportTask task) {
+		threadPool.execute(() -> {
+			log.info("ExportService::exporting... task id is " + task.getId());
+			long start = System.currentTimeMillis();
+			String error = handle(task);
+			if (StrUtil.isBlankIfStr(error)) {
+				task.setStatus(DataExportTaskStatus.success.getCode());
+				log.info("ExportService::export success, task id is " + task.getId());
+			} else {
+				task.setStatus(DataExportTaskStatus.failure.getCode());
+				log.info("ExportService::export failure, task id is " + task.getId());
+				deleteCache(task);
+			}
+			long end = System.currentTimeMillis();
+			float takeUpTime = (end - start) / 1000.f;
+			task.setExt(error);
+			task.setFinishTime(new Date());
+			task.setTakeUpTime(BigDecimal.valueOf(takeUpTime));
+			dataExportTaskService.saveOrUpdate(task);
+			log.info("ExportService::export task take up time is " + takeUpTime + "second");
+
+		});
+	}
+
+	private String handle(DataExportTask task) {
+		String error = null;
+		try {
+			File excelFile = new File(getFilePath(task.getFileName()));
+			VisualdevEntity form = visualdevService.getInfo(task.getModelId());
+			// 已经查寻的数量
+			long total = 0;
+			long page = 1;
+			while (true) {
+				if (total >= 1000000) {
+					// 受excel限制最多导出100万条数据
+					break;
+				}
+				PaginationModel pagination = new PaginationModel();
+				pagination.setCurrentPage(page);
+				pagination.setPageSize(PAGE_SIZE);
+				PageModel<JSONObject> pageData = formDataWithTableService.listPage(form, pagination);
+				if (pageData != null && CollUtil.isNotEmpty(pageData.getRecords())) {
+					// 有数据
+					page = pageData.getCurrent() + 1;
+					List<JSONObject> dataList = pageData.getRecords();
+					total += dataList.size();
+					error = writeToExcel(task, dataList);
+					if (StrUtil.isBlankIfStr(error)) {
+						// 写入excel成功
+					} else {
+						// 写入excel失败
+						break;
+					}
+				} else {
+					// 数据查询完毕
+					break;
+				}
+			}
+			task.setFileSize(excelFile.length());
+			task.setCount(total);
+		} catch (Exception e) {
+			error = e.getMessage();
+		}
+		return error;
+	}
+
+	private String writeToExcel(DataExportTask task, List<JSONObject> retList) {
+		try {
+			List<List<Object>> excelList = new ArrayList<List<Object>>();
+			List<RelationField> fieldList = JsonUtil.getJsonToList(task.getRule(), RelationField.class);
+			retList.forEach(ret -> {
+				List<Object> rowList = new ArrayList<Object>();
+				fieldList.forEach(field -> {
+					int fieldId = field.getFieldId();
+					if (ret.containsKey(String.valueOf(fieldId))) {
+						Object value = ret.get(String.valueOf(fieldId));
+						rowList.add(value != null ? value : "");
+					}
+				});
+				excelList.add(rowList);
+			});
+			EasyExcel.write(getFilePath(task.getFileName()))
+					.head(buidlHeader(task))
+					.sheet(task.getName())
+					.doWrite(excelList);
+		} catch (Exception e) {
+			return e.getMessage();
+		}
+		return null;
+	}
+
+	/**
+	 * 动态生成表头
+	 */
+	private List<List<String>> buidlHeader(DataExportTask task) {
+		List<RelationField> fieldList = JsonUtil.getJsonToList(task.getRule(), RelationField.class);
+		List<List<String>> headerList = new ArrayList<List<String>>();
+		fieldList.forEach(column -> {
+			List<String> head = new ArrayList<String>();
+			head.add(column.getFieldName());
+			headerList.add(head);
+		});
+		return headerList;
+	}
+
+	private void deleteCache(DataExportTask task) {
+		try {
+			dataExportTaskService.saveOrUpdate(task);
+			// 删除导出失败的垃圾文件
+			FileUtils.delete(new File(getFilePath(task.getFileName())));
+		} catch (Exception e) {
+			log.info("ExportService::delete cache file failure, task id is" + task.getId() + ", error is "
+					+ e.getMessage());
+		}
+	}
+
+	private String getFilePath(String fileName) {
+		File dir = new File(formDir);
+		dir.mkdirs();
+		return formDir + fileName;
+	}
+
+	public static void main(String[] agrs) {
+		List<List<String>> headerList = new ArrayList<List<String>>();
+		List<String> header1 = new ArrayList<>();
+		header1.add("h1");
+		headerList.add(header1);
+
+		List<String> header2 = new ArrayList<>();
+		header2.add("h2");
+		headerList.add(header2);
+
+		List<String> header3 = new ArrayList<>();
+		header3.add("h3");
+		headerList.add(header3);
+
+		List<List<Object>> excelList = new ArrayList<List<Object>>();
+		List<Object> rowList = new ArrayList<Object>();
+		rowList.add(1);
+		rowList.add("a");
+		rowList.add("bbb");
+		excelList.add(rowList);
+
+		log.info("headerList is " + JSON.toJSONString(headerList));
+		log.info("excelList is " + JSON.toJSONString(excelList));
+
+		File excelFile = new File("D://www/upload/form/test.xlsx");
+		EasyExcel.write(excelFile.getPath()).head(headerList).sheet("模板").doWrite(excelList);
+	}
+}

+ 68 - 0
src/main/java/org/springblade/flow/datalog/controller/DataExportTaskController.java

@@ -0,0 +1,68 @@
+package org.springblade.flow.datalog.controller;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.flow.datalog.model.DataExportTask;
+import org.springblade.flow.datalog.service.DataExportTaskService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 
+ * @author William
+ * @date 2022年12月2日 下午4:34:46
+ */
+@RestController
+@RequestMapping("/paas/visualdev/data/export")
+@Api(tags = "AA表单数据导出任务接口", value = "DataExportTaskLog")
+public class DataExportTaskController {
+
+	@Value("${blade.file.formDir}")
+	private String formDir;
+
+	@Autowired
+	private DataExportTaskService exportTaskService;
+
+	@PostMapping("/list")
+	@ApiOperation(value = "分页查询")
+	public R<IPage<DataExportTask>> list(String modelId, Query query) {
+		LambdaQueryWrapper<DataExportTask> wrapper = Wrappers.<DataExportTask>lambdaQuery();
+		wrapper.eq(DataExportTask::getModelId, modelId);
+		wrapper.orderByDesc(DataExportTask::getId);
+		return R.data(exportTaskService.page(Condition.getPage(query), wrapper));
+	}
+
+	@PostMapping("/download")
+	@ApiOperation(value = "下载Excel文件")
+	public void download(Long id, HttpServletResponse response) throws IORuntimeException, IOException {
+		DataExportTask task = exportTaskService.getById(id);
+		File file = new File(formDir + task.getFileName());
+		if (file != null && file.exists() && file.isFile()) {
+			response.setContentType("application/vnd.ms-excel");
+			response.setCharacterEncoding("utf-8");
+			String fileName = URLEncoder.encode(task.getName(), "UTF-8");
+			response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
+			response.getOutputStream().write(FileUtil.readBytes(file));
+			response.getOutputStream().flush();
+		}
+	}
+}

+ 2 - 39
src/main/java/org/springblade/flow/datalog/controller/FormDataLogController.java

@@ -29,7 +29,7 @@ import io.swagger.annotations.ApiOperation;
  */
 @RestController
 @RequestMapping("/paas/visualdev/datalog")
-@Api(tags = "表单数据操作记录", value = "FormDataLog")
+@Api(tags = "AA表单数据操作记录", value = "FormDataLog")
 public class FormDataLogController {
 
 	@Autowired
@@ -74,44 +74,6 @@ public class FormDataLogController {
 		return R.data(null);
 	}
 
-//	@GetMapping("/all")
-//	@ApiOperation(value = "表单所有数据变更记录")
-//	public R<List<Kv>> all(String formId) {
-//		LambdaQueryWrapper<FormDataLog> wrapper = Wrappers.<FormDataLog>lambdaQuery();
-//		wrapper.eq(FormDataLog::getFormId, formId);
-//		wrapper.orderByDesc(FormDataLog::getSn);
-//		LinkedHashMap<Long, List<FormDataLog>> map = new LinkedHashMap<>();
-//		List<FormDataLog> all = logService.list(wrapper);
-//		if (CollUtil.isNotEmpty(all)) {
-//			all.forEach(item -> {
-//				List<FormDataLog> list = new ArrayList<>();
-//				if (map.containsKey(item.getSn())) {
-//					list = map.get(item.getSn());
-//				}else {
-//					map.put(item.getSn(), list);
-//				}
-//				list.add(item);
-//			});
-//		}
-//
-//		if (CollUtil.isNotEmpty(map)) {
-//			List<Kv> allList = new ArrayList<>();
-//			Set<Long> keys = map.keySet();
-//			keys.forEach(key -> {
-//				List<FormDataLog> childList = map.get(key);
-//				Kv kv = Kv.create();
-//				kv.set("sn", key);
-//				kv.set("time", childList.get(0).getTime());
-//				kv.set("operator", childList.get(0).getOperator());
-//				kv.set("items", childList);
-//				allList.add(kv);
-//			});
-//			return R.data(allList);
-//		}
-//
-//		return R.data(null);
-//	}
-
 //	@PostMapping("/list")
 //	@ApiOperation(value = "分页查询")
 //	public R<IPage<FormDataLog>> list(String formId, Query query) {
@@ -120,4 +82,5 @@ public class FormDataLogController {
 //		wrapper.orderByDesc(FormDataLog::getId);
 //		return R.data(logService.page(Condition.getPage(query), wrapper));
 //	}
+	
 }

+ 41 - 0
src/main/java/org/springblade/flow/datalog/enums/DataExportTaskStatus.java

@@ -0,0 +1,41 @@
+package org.springblade.flow.datalog.enums;
+
+/**
+ * 表单数据导出状态
+ * 
+ * @author William
+ * @date 2022年11月17日 上午10:14:59
+ */
+public enum DataExportTaskStatus {
+
+	doing(0, "正在导出"),
+
+	success(1, "成功"),
+
+	failure(2, "失败"),;
+
+	private int code;
+	private String message;
+
+	private DataExportTaskStatus(int code, String message) {
+		this.code = code;
+		this.message = message;
+	}
+
+	public int getCode() {
+		return code;
+	}
+
+	public void setCode(int code) {
+		this.code = code;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+}

+ 14 - 0
src/main/java/org/springblade/flow/datalog/mapper/DataExportTaskMapper.java

@@ -0,0 +1,14 @@
+package org.springblade.flow.datalog.mapper;
+
+import org.springblade.flow.datalog.model.DataExportTask;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 
+ * @author William
+ * @date 2022年12月2日 下午12:24:22
+ */
+public interface DataExportTaskMapper extends BaseMapper<DataExportTask> {
+
+}

+ 1 - 1
src/main/java/org/springblade/flow/datalog/mapper/FormDataLogMapper.java

@@ -9,6 +9,6 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  * @author William
  * @date 2022年11月17日 上午11:55:24
  */
-public interface FormDataLogMapper  extends BaseMapper<FormDataLog>{
+public interface FormDataLogMapper extends BaseMapper<FormDataLog>{
 	
 }

+ 74 - 0
src/main/java/org/springblade/flow/datalog/model/DataExportTask.java

@@ -0,0 +1,74 @@
+package org.springblade.flow.datalog.model;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 表单数据导出实体类
+ * 
+ * @author William
+ * @date 2022年12月2日 上午11:18:34
+ */
+@Data
+@Builder
+@TableName("base_visualdev_export_task")
+@ApiModel(value = "visualdev_export_task", description = "系统菜单")
+public class DataExportTask {
+	@ApiModelProperty(value = "ID", name = "id", required = true)
+	@TableId(type = IdType.ASSIGN_ID)
+	private Long id;
+
+	@ApiModelProperty(value = "任务名称", name = "name", required = true)
+	private String name;
+
+	@ApiModelProperty(value = "表单ID", name = "modelId", required = true)
+	private String modelId;
+
+	@ApiModelProperty(value = "数据集ID", name = "cubeId", required = false)
+	private String cubeId;
+
+	@ApiModelProperty(value = "是否导出全部(0-非全部;1-全部)", name = "isAll", required = true)
+	private int isAll;
+
+	@ApiModelProperty(value = "导出规则", name = "rule", required = false)
+	private String rule;
+
+	@ApiModelProperty(value = "操作人", name = "operator", required = false)
+	private String operator;
+
+	@ApiModelProperty(value = "操作人ID", name = "operatorId", required = false)
+	private Long operatorId;
+
+	@ApiModelProperty(value = "创建时间", name = "createTime", required = false)
+	private Date createTime;
+
+	@ApiModelProperty(value = "完成时间", name = "finishTime", required = false)
+	private Date finishTime;
+
+	@ApiModelProperty(value = "任务状态(0-进行中;1-成功;2-失败;)", name = "status", required = false)
+	private int status;
+
+	@ApiModelProperty(value = "文件名", name = "fileName", required = false)
+	private String fileName;
+	
+	@ApiModelProperty(value = "文件大小", name = "fileSize", required = false)
+	private long fileSize;
+	
+	@ApiModelProperty(value = "导出条数", name = "count", required = false)
+	private long count;
+
+	@ApiModelProperty(value = "扩展信息,比如导出失败时的错误信息", name = "ext", required = false)
+	private String ext;
+	
+	@ApiModelProperty(value = "耗时(单位:秒)", name = "takeUpTime", required = false)
+	private BigDecimal takeUpTime;
+}

+ 32 - 0
src/main/java/org/springblade/flow/datalog/params/DataExportParam.java

@@ -0,0 +1,32 @@
+package org.springblade.flow.datalog.params;
+
+import java.util.List;
+
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 数据导出请求参数实体类
+ * 
+ * @author William
+ * @date 2022年12月2日 上午10:26:28
+ */
+@Data
+public class DataExportParam {
+	@ApiModelProperty(value = "表单ID", required = true)
+	private String modelId;
+
+	@ApiModelProperty(value = "任务名称", name = "taskName", required = true)
+	private String taskName;
+
+	@ApiModelProperty(value = "是否导出所有数据(true-全部,false=非全部)", required = true)
+	private boolean isAll;
+	
+	@ApiModelProperty(value = "要导出字段在数据集中的ID数组", required = true)
+	private List<Integer> columnIdList;
+
+	@ApiModelProperty(value = "要导出的数据ID数组,isAll=true时必传", required = false)
+	private List<String> dataIdList;
+
+}

+ 17 - 0
src/main/java/org/springblade/flow/datalog/service/DataExportTaskService.java

@@ -0,0 +1,17 @@
+package org.springblade.flow.datalog.service;
+
+import org.springblade.flow.datalog.mapper.DataExportTaskMapper;
+import org.springblade.flow.datalog.model.DataExportTask;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+/**
+ * 
+ * @author William
+ * @date 2022年11月17日 上午11:55:38
+ */
+@Service
+public class DataExportTaskService extends ServiceImpl<DataExportTaskMapper, DataExportTask> {
+
+}

+ 126 - 88
src/main/java/org/springblade/flow/visual/onlinedev/common/formdata/FormDataWithTableService.java

@@ -69,7 +69,7 @@ public class FormDataWithTableService {
 
 	@Autowired
 	private DataSourceService dataSourceService;
-	
+
 	/**
 	 * 获取某个表单对应的主表的主键字段在数据集中的ID
 	 * 
@@ -87,7 +87,8 @@ public class FormDataWithTableService {
 		Column primaryColumn = fly.getPrimaryColumn(parentRf.getTable());
 		if (primaryColumn != null) {
 			String primaryKey = primaryColumn.getColumnName();
-			RelationField pkRf = RelationUtil.getRelationFieldByFieldAndTable(primaryKey, parentRf.getTable(),
+			RelationField pkRf = RelationUtil.getRelationFieldByFieldAndTable(primaryKey,
+					parentRf.getTable(),
 					parentRf.getFields());
 			if (pkRf != null) {
 				primaryKeyId = Integer.valueOf(pkRf.getFieldId());
@@ -114,9 +115,9 @@ public class FormDataWithTableService {
 		String sql = buildListSql(relationTree, ds);
 		log.info("list sql is " + sql);
 
-//		if (StringUtil.isNotEmpty(paginationModel.getJson())) {
-//			// 有条件查询
-//		}
+		// if (StringUtil.isNotEmpty(paginationModel.getJson())) {
+		// // 有条件查询
+		// }
 		CubeDataModel cubeData = dataSourceService.getCubeData(formInfo.getDbLinkId());
 		DataSourceModel dataSource = dataSourceService.getReportDatasource(String.valueOf(cubeData.getId()));
 		Page<JSONObject> page = new Page<JSONObject>(paginationModel.getCurrentPage(), paginationModel.getPageSize())
@@ -129,7 +130,8 @@ public class FormDataWithTableService {
 		Column primaryColumn = fly.getPrimaryColumn(parentRf.getTable());
 		if (primaryColumn != null) {
 			String primaryKey = primaryColumn.getColumnName();
-			RelationField pkRf = RelationUtil.getRelationFieldByFieldAndTable(primaryKey, parentRf.getTable(),
+			RelationField pkRf = RelationUtil.getRelationFieldByFieldAndTable(primaryKey,
+					parentRf.getTable(),
 					parentRf.getFields());
 			if (pkRf != null) {
 				primaryKeyId = Integer.valueOf(pkRf.getFieldId());
@@ -140,6 +142,31 @@ public class FormDataWithTableService {
 	}
 
 	/**
+	 * 分页查询 仅返回分页数据
+	 * 
+	 * @param formInfo
+	 * @param paginationModel
+	 * @return
+	 */
+	public PageModel<JSONObject> listPage(VisualdevEntity formInfo, PaginationModel paginationModel) {
+		DataSourceModel ds = dataSourceService.getReportDatasource(formInfo.getDbLinkId());
+		if (ds == null) {
+			return new PageModel<JSONObject>();
+		}
+
+		List<RelationBean> relationTree = dataSourceService.getRelationTree(formInfo.getTables(),
+				formInfo.getDbLinkId());
+		String sql = buildListSql(relationTree, ds);
+		log.info("list sql is " + sql);
+		CubeDataModel cubeData = dataSourceService.getCubeData(formInfo.getDbLinkId());
+		DataSourceModel dataSource = dataSourceService.getReportDatasource(String.valueOf(cubeData.getId()));
+		Page<JSONObject> page = new Page<JSONObject>(paginationModel.getCurrentPage(), paginationModel.getPageSize())
+				.setSearchCount(true);
+		PageModel<JSONObject> pageData = dynamicDataService.selectPage(page, dataSource, sql);
+		return pageData;
+	}
+
+	/**
 	 * 构造完整的查询语句
 	 * 
 	 * @param relationTree
@@ -269,7 +296,8 @@ public class FormDataWithTableService {
 		// 左查询
 		boolean b = false;
 		if (colData.getTreeRelation() != null) {
-			b = keyJsonMap.keySet().stream()
+			b = keyJsonMap.keySet()
+					.stream()
 					.anyMatch(t -> t.equalsIgnoreCase(String.valueOf(colData.getTreeRelation())));
 		}
 		if (b && keyJsonMap.size() > searchVOList.size()) {
@@ -308,13 +336,13 @@ public class FormDataWithTableService {
 	/**
 	 * 多表批量导入数据
 	 * 
-	 * @param cubeData 数据集信息
+	 * @param cubeData   数据集信息
 	 * @param withHeader 是否带表头
-	 * @param formInfo 表单信息
-	 * @param indexList 字段和excel索引映射关系
-	 * @param excelFile excel文件
+	 * @param formInfo   表单信息
+	 * @param indexList  字段和excel索引映射关系
+	 * @param excelFile  excel文件
 	 * @return
-	 * @throws SQLException 
+	 * @throws SQLException
 	 */
 	public ActionResult<String> batchAddForMultipleTable(CubeDataModel cubeData, boolean withHeader,
 			VisualdevEntity formInfo, List<ExcelFieldIndex> indexList, File excelFile) throws SQLException {
@@ -328,13 +356,13 @@ public class FormDataWithTableService {
 	/**
 	 * 单表批量导入数据
 	 * 
-	 * @param cubeData 数据集信息
+	 * @param cubeData   数据集信息
 	 * @param withHeader 是否带表头
-	 * @param formInfo 表单信息
-	 * @param indexList 字段和excel索引映射关系
-	 * @param excelFile excel文件
+	 * @param formInfo   表单信息
+	 * @param indexList  字段和excel索引映射关系
+	 * @param excelFile  excel文件
 	 * @return
-	 * @throws SQLException 
+	 * @throws SQLException
 	 */
 	public ActionResult<String> batchAddForSingleTable(CubeDataModel cubeData, boolean withHeader,
 			VisualdevEntity formInfo, List<ExcelFieldIndex> indexList, File excelFile) throws SQLException {
@@ -348,7 +376,7 @@ public class FormDataWithTableService {
 	/**
 	 * 提交表单数据入库
 	 * 
-	 * @param modelId  表单ID
+	 * @param modelId                  表单ID
 	 * @param visualdevModelDataCrForm 前端提交的数据参数
 	 * @return
 	 */
@@ -367,7 +395,7 @@ public class FormDataWithTableService {
 		List<RelationBean> relationTree = dataSourceService.getRelationTree(formInfo.getTables(),
 				formInfo.getDbLinkId());
 		Map<String, Object> formDataMap = JsonUtil.stringToMap(visualdevModelDataCrForm.getData());
-//		log.info("formDataMap is " + JSON.toJSONString(formDataMap, true));
+		// log.info("formDataMap is " + JSON.toJSONString(formDataMap, true));
 		recursionInsertData(cubeData, ds, relationTree, formDataMap, null);
 		return ActionResult.success("操作成功");
 	}
@@ -375,10 +403,10 @@ public class FormDataWithTableService {
 	/**
 	 * 递归插入数据
 	 * 
-	 * @param cubeData 数据集
-	 * @param ds 数据库连接对象
-	 * @param relationTree 表关联及字段信息
-	 * @param formDataMap 前端提交的所有数据集合
+	 * @param cubeData        数据集
+	 * @param ds              数据库连接对象
+	 * @param relationTree    表关联及字段信息
+	 * @param formDataMap     前端提交的所有数据集合
 	 * @param relationDataMap 父表关联字段数据
 	 */
 	private void recursionInsertData(CubeDataModel cubeData, DataSourceModel ds, List<RelationBean> relationTree,
@@ -596,7 +624,7 @@ public class FormDataWithTableService {
 			String sql = sqlBuffer.toString();
 			log.info("batch delete sql is : " + sql);
 			dynamicDataService.executeSql(ds, sql);
-			return ActionResult.fail("批量删除成功");
+			return ActionResult.success("批量删除成功");
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
@@ -642,8 +670,8 @@ public class FormDataWithTableService {
 	/**
 	 * 更新数据
 	 * 
-	 * @param formInfo 表单信息
-	 * @param id 数据主键
+	 * @param formInfo                 表单信息
+	 * @param id                       数据主键
 	 * @param visualdevModelDataUpForm 新数据
 	 * @return
 	 */
@@ -664,7 +692,7 @@ public class FormDataWithTableService {
 		if (primaryColumn == null) {
 			return ActionResult.fail("更新失败,该表不存在主键,表名是:" + mainTable);
 		}
-		
+
 		Map<String, Object> formDataMap = JsonUtil.stringToMap(visualdevModelDataUpForm.getData());
 		List<UpdateBean> updateList = new ArrayList<>();
 		DsType dsType = DsType.reslove(ds.getDsType());
@@ -680,14 +708,14 @@ public class FormDataWithTableService {
 	/**
 	 * 递归构造更新对象
 	 * 
-	 * @param ds 数据库连接
-	 * @param dsType 数据库类型
-	 * @param mainTableId 主表的主键
-	 * @param formDataMap 前端提交的要更新的数据
+	 * @param ds                  数据库连接
+	 * @param dsType              数据库类型
+	 * @param mainTableId         主表的主键
+	 * @param formDataMap         前端提交的要更新的数据
 	 * @param preTableOldDataList 上个表的旧数据
 	 * @param preTableNewDataList 上个表的新数据
-	 * @param relationTree 数据集关联关系的树形结构
-	 * @param updateList 要构造的更新条件结构列表
+	 * @param relationTree        数据集关联关系的树形结构
+	 * @param updateList          要构造的更新条件结构列表
 	 */
 	private void recursionBuildUpdate(DataSourceModel ds, DsType dsType, String mainTableId,
 			Map<String, Object> formDataMap, List<Map<String, Object>> preTableOldDataList,
@@ -812,65 +840,75 @@ public class FormDataWithTableService {
 
 			if (CollUtil.isNotEmpty(relation.getChildren())) {
 				// 有子表,则继续递归
-				recursionBuildUpdate(ds, dsType, null, formDataMap, curTableOldDataList, newColumnList,
-						relation.getChildren(), updateList);
+				recursionBuildUpdate(ds,
+						dsType,
+						null,
+						formDataMap,
+						curTableOldDataList,
+						newColumnList,
+						relation.getChildren(),
+						updateList);
 			}
 		}
 	}
 
-//	private ActionResult<String> updateForSingleTable(Column primaryColumn, String tableName, DataSourceModel ds,
-//			Map<String, Object> formDataMap, String id) {
-//		try {
-//			DsType dsType = DsType.reslove(ds.getDsType());
-//			StringBuffer sqlBuffer = new StringBuffer();
-//			sqlBuffer.append("update " + tableName + " ");
-//
-//			StringBuffer valueSql = new StringBuffer("set ");
-//			formDataMap.forEach((key, object) -> {
-//				String value = object == null || object.toString().equalsIgnoreCase("[]") ? null : object.toString();
-//				CubeTableColumnModel field = dataSourceService.getFieldInfo(Integer.parseInt(key));
-//				String fieldName = field.getColumnName();
-//
-//				if (value != null) {
-//					ViewColumnTypeEnum dataType = ViewColumnTypeEnum.resolve(field.getViewDataType());
-//					if (dataType == ViewColumnTypeEnum.STRING) {
-//						value = "'" + value + "'";
-//					} else if (dataType == ViewColumnTypeEnum.DATETIME) {
-//						// 时间日期类型 前端统一传递的都是时间戳
-//						long timestamp = Long.decode(value);
-//						value = DateUtil.daFormat(timestamp);
-//						value = "'" + value + "'";
-//						if (dsType == DsType.Oracle) {
-//							value = "to_date(" + value + ",'yyyy-mm-dd HH24:mi:ss')";
-//						}
-//					}
-//				}
-//				if (!primaryColumn.getColumnName().equalsIgnoreCase(fieldName)) {
-//					// 不是主键
-//					valueSql.append(fieldName + "=" + value);
-//					valueSql.append(",");
-//				}
-//			});
-//
-//			String vsql = valueSql.toString();
-//			vsql = vsql.substring(0, vsql.length() - 1);
-//			sqlBuffer.append(vsql);
-//			sqlBuffer.append(" where " + primaryColumn.getColumnName() + "=");
-//			String symbol = "";
-//			if (primaryColumn.getJavaType().equalsIgnoreCase("String")) {
-//				// 主键是字符串
-//				symbol = "'";
-//			}
-//			sqlBuffer.append(symbol + id + symbol + " ");
-//
-//			String sql = sqlBuffer.toString();
-//			log.info("update sql of single table is " + sql);
-//			dynamicDataService.executeSql(ds, sql);
-//			return ActionResult.success("更新成功");
-//		} catch (Exception e) {
-//		}
-//		return ActionResult.fail("更新失败");
-//	}
+	// private ActionResult<String> updateForSingleTable(Column primaryColumn,
+	// String tableName, DataSourceModel ds,
+	// Map<String, Object> formDataMap, String id) {
+	// try {
+	// DsType dsType = DsType.reslove(ds.getDsType());
+	// StringBuffer sqlBuffer = new StringBuffer();
+	// sqlBuffer.append("update " + tableName + " ");
+	//
+	// StringBuffer valueSql = new StringBuffer("set ");
+	// formDataMap.forEach((key, object) -> {
+	// String value = object == null || object.toString().equalsIgnoreCase("[]") ?
+	// null : object.toString();
+	// CubeTableColumnModel field =
+	// dataSourceService.getFieldInfo(Integer.parseInt(key));
+	// String fieldName = field.getColumnName();
+	//
+	// if (value != null) {
+	// ViewColumnTypeEnum dataType =
+	// ViewColumnTypeEnum.resolve(field.getViewDataType());
+	// if (dataType == ViewColumnTypeEnum.STRING) {
+	// value = "'" + value + "'";
+	// } else if (dataType == ViewColumnTypeEnum.DATETIME) {
+	// // 时间日期类型 前端统一传递的都是时间戳
+	// long timestamp = Long.decode(value);
+	// value = DateUtil.daFormat(timestamp);
+	// value = "'" + value + "'";
+	// if (dsType == DsType.Oracle) {
+	// value = "to_date(" + value + ",'yyyy-mm-dd HH24:mi:ss')";
+	// }
+	// }
+	// }
+	// if (!primaryColumn.getColumnName().equalsIgnoreCase(fieldName)) {
+	// // 不是主键
+	// valueSql.append(fieldName + "=" + value);
+	// valueSql.append(",");
+	// }
+	// });
+	//
+	// String vsql = valueSql.toString();
+	// vsql = vsql.substring(0, vsql.length() - 1);
+	// sqlBuffer.append(vsql);
+	// sqlBuffer.append(" where " + primaryColumn.getColumnName() + "=");
+	// String symbol = "";
+	// if (primaryColumn.getJavaType().equalsIgnoreCase("String")) {
+	// // 主键是字符串
+	// symbol = "'";
+	// }
+	// sqlBuffer.append(symbol + id + symbol + " ");
+	//
+	// String sql = sqlBuffer.toString();
+	// log.info("update sql of single table is " + sql);
+	// dynamicDataService.executeSql(ds, sql);
+	// return ActionResult.success("更新成功");
+	// } catch (Exception e) {
+	// }
+	// return ActionResult.fail("更新失败");
+	// }
 
 	/**
 	 * 获取某个表的主键字段

+ 81 - 25
src/main/java/org/springblade/flow/visual/onlinedev/controller/VisualdevModelDataController.java

@@ -6,6 +6,7 @@ import java.sql.SQLException;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -14,11 +15,16 @@ import javax.annotation.Resource;
 import org.flowable.engine.RuntimeService;
 import org.springblade.bi.client.model.CubeDataModel;
 import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.secure.utils.SecureUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.flow.base.ActionResult;
 import org.springblade.flow.base.util.FlowDataUtil;
 import org.springblade.flow.config.ConfigValueUtil;
 import org.springblade.flow.database.exception.DataException;
+import org.springblade.flow.datalog.common.ExportService;
+import org.springblade.flow.datalog.model.DataExportTask;
+import org.springblade.flow.datalog.params.DataExportParam;
+import org.springblade.flow.datalog.service.DataExportTaskService;
 import org.springblade.flow.datalog.service.FormDataLogService;
 import org.springblade.flow.engine.entity.FlowEngineEntity;
 import org.springblade.flow.engine.service.FlowEngineService;
@@ -44,6 +50,7 @@ import org.springblade.flow.visual.base.util.VisualUtils;
 import org.springblade.flow.visual.onlinedev.common.dynamic.DataSourceService;
 import org.springblade.flow.visual.onlinedev.common.excel.ExcelFormParam;
 import org.springblade.flow.visual.onlinedev.common.formdata.FormDataWithTableService;
+import org.springblade.flow.visual.onlinedev.common.relation.RelationField;
 import org.springblade.flow.visual.onlinedev.common.relation.RelationUtil;
 import org.springblade.flow.visual.onlinedev.entity.VisualdevModelDataEntity;
 import org.springblade.flow.visual.onlinedev.model.BatchRemoveIdsVo;
@@ -69,6 +76,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.github.xiaoymin.knife4j.core.util.StrUtil;
 
@@ -86,7 +94,7 @@ import lombok.extern.slf4j.Slf4j;
  * @date 2019年9月27日 上午9:18
  */
 @Slf4j
-@Api(tags = "0表单在线设计相关接口", value = "OnlineDev")
+@Api(tags = "AA表单在线设计相关接口", value = "OnlineDev")
 @RestController
 @RequestMapping("/paas/visualdev/OnlineDev")
 public class VisualdevModelDataController extends BladeController {
@@ -107,7 +115,7 @@ public class VisualdevModelDataController extends BladeController {
 	private FlowEngineService flowEngineService;
 
 	@Autowired
-	private DataSourceService dblinkService;
+	private DataSourceService dataSourceService;
 	@Resource
 	private RuntimeService runtimeService;
 
@@ -129,9 +137,15 @@ public class VisualdevModelDataController extends BladeController {
 	@Autowired
 	private FormDataLogService logService;
 
+	@Autowired
+	private DataExportTaskService dataExportTaskService;
+
 	@Value("${blade.file.uploadPath}")
 	private String uploadDir;
 
+	@Autowired
+	private ExportService exportService;
+
 	@ApiOperation("表单操作 - 获取列表表单配置JSON")
 	@GetMapping("/{modelId}/Config")
 	public ActionResult<Object> getData(@PathVariable("modelId") String modelId) {
@@ -262,14 +276,20 @@ public class VisualdevModelDataController extends BladeController {
 			return ActionResult.fail("表单不存在");
 		}
 
-		CubeDataModel cubeData = dblinkService.getCubeData(formInfo.getDbLinkId());
+		CubeDataModel cubeData = dataSourceService.getCubeData(formInfo.getDbLinkId());
 		if (RelationUtil.hasLinkTable(cubeData)) {
-			return formDataWithTableService.batchAddForMultipleTable(cubeData, params.getWithHeader(), formInfo,
-					params.getList(), excelFile);
+			return formDataWithTableService.batchAddForMultipleTable(cubeData,
+					params.getWithHeader(),
+					formInfo,
+					params.getList(),
+					excelFile);
 		}
 
-		return formDataWithTableService.batchAddForSingleTable(cubeData, params.getWithHeader(), formInfo,
-				params.getList(), excelFile);
+		return formDataWithTableService.batchAddForSingleTable(cubeData,
+				params.getWithHeader(),
+				formInfo,
+				params.getList(),
+				excelFile);
 	}
 
 	@ApiOperation("数据操作 - 更新")
@@ -333,7 +353,7 @@ public class VisualdevModelDataController extends BladeController {
 
 	@ApiOperation("数据操作 - 查询操作权限")
 	@PostMapping("/getPerm/{modelId}")
-	public R getPerm(@PathVariable("modelId") String modelId) {
+	public R<Object> getPerm(@PathVariable("modelId") String modelId) {
 		Long userId = getUser().getUserId();
 		List<String> result = new ArrayList<>();
 		// 查询用户所属权限
@@ -342,7 +362,8 @@ public class VisualdevModelDataController extends BladeController {
 		if (dataPermissionUsers != null && dataPermissionUsers.size() > 0) {
 			for (DataPermissionUser dataPermissionUser : dataPermissionUsers) {
 				VisualdevDataPermission visualdevDataPermission = dataPermissionService.getOne(Wrappers
-						.<VisualdevDataPermission>lambdaQuery().eq(VisualdevDataPermission::getVisualdevId, modelId)
+						.<VisualdevDataPermission>lambdaQuery()
+						.eq(VisualdevDataPermission::getVisualdevId, modelId)
 						.eq(VisualdevDataPermission::getDataPermId, dataPermissionUser.getDataPermId()));
 				if (visualdevDataPermission != null) {
 					String permJson = visualdevDataPermission.getPermJson();
@@ -359,7 +380,8 @@ public class VisualdevModelDataController extends BladeController {
 		if (result.size() == 0) {
 			// 取默认权限
 			VisualdevDataPermission visualdevDataPermission = dataPermissionService.getOne(
-					Wrappers.<VisualdevDataPermission>lambdaQuery().eq(VisualdevDataPermission::getVisualdevId, modelId)
+					Wrappers.<VisualdevDataPermission>lambdaQuery()
+							.eq(VisualdevDataPermission::getVisualdevId, modelId)
 							.eq(VisualdevDataPermission::getIsDefault, 1));
 			if (visualdevDataPermission != null) {
 				String permJson = visualdevDataPermission.getPermJson();
@@ -376,20 +398,54 @@ public class VisualdevModelDataController extends BladeController {
 		return R.data(result);
 	}
 
-//	@ApiOperation("数据操作 - 导出")
-//	@PostMapping("/{modelId}/Actions/Export")
-//	public ActionResult<DownloadVO> export(@PathVariable("modelId") String modelId,
-//			@RequestBody PaginationModelExport paginationModelExport)
-//			throws ParseException, IOException, SQLException, DataException {
-//		VisualdevEntity visualdevEntity = visualdevService.getInfo(modelId);
-//		String[] keys = paginationModelExport.getSelectKey();
-//		// 关键字过滤
-//		List<Map<String, Object>> realList = visualdevModelDataService.exportData(keys, paginationModelExport,
-//				visualdevEntity);
-//		UserInfo userInfo = userProvider.get();
-//		DownloadVO vo = VisualUtils.createModelExcel(visualdevEntity.getFormData(),
-//				configValueUtil.getTemporaryFilePath(), realList, keys, userInfo);
-//		return ActionResult.success(vo);
-//	}
+	@ApiOperation("数据操作 - 导出")
+	@PostMapping("/export")
+	public ActionResult<DataExportTask> export(@RequestBody DataExportParam param) {
+		if (StrUtil.isBlank(param.getModelId())) {
+			return ActionResult.fail("表单ID不能为空", null);
+		}
+		if (StrUtil.isBlank(param.getTaskName())) {
+			return ActionResult.fail("任务名称不能为空", null);
+		}
+		if (CollUtil.isEmpty(param.getColumnIdList())) {
+			return ActionResult.fail("要导出的字段ID不能为空", null);
+		}
+		if (!param.isAll() && CollUtil.isEmpty(param.getDataIdList())) {
+			return ActionResult.fail("数据ID不能为空", null);
+		}
+
+		VisualdevEntity form = visualdevService.getInfo(param.getModelId());
+		long taskId = IdWorker.getId();
+		DataExportTask task = DataExportTask.builder()
+				.id(taskId)
+				.modelId(param.getModelId())
+				.cubeId(form.getDbLinkId())
+				.name(param.getTaskName())
+				.isAll(param.isAll() ? 1 : 0)
+				.name(param.getTaskName())
+				.createTime(new Date())
+				.operator(SecureUtil.getNickName())
+				.operatorId(SecureUtil.getUserId())
+				.fileName(taskId + ".xlsx")
+				.build();
+		// 组装导出规则json
+		List<RelationField> fields = JsonUtil.getJsonToList(form.getTables(), RelationField.class);
+		List<RelationField> rule = new ArrayList<>();
+		List<Integer> columnIdList = param.getColumnIdList();
+		for (int i = 0; i < columnIdList.size(); i++) {
+			Integer columnId = columnIdList.get(i);
+			fields.forEach(field -> {
+				if (field.getFieldId() == columnId) {
+					rule.add(field);
+				}
+			});
+		}
+		task.setRule(JSON.toJSONString(rule));
+		boolean flag = dataExportTaskService.save(task);
+		if (flag) {
+			exportService.excute(task);
+		}
+		return ActionResult.status(flag, flag ? task : null);
+	}
 
 }

+ 1 - 0
src/main/resources/application-dev.yml

@@ -44,6 +44,7 @@ spring:
 blade:
    file:
       uploadPath: D://www/upload/
+      formDir: D://www/upload/form/
 
 config:
   # Windows配置(静态资源根目录和代码生成器临时目录)

+ 1 - 0
src/main/resources/application-prod.yml

@@ -43,6 +43,7 @@ spring:
 blade:
    file:
       uploadPath: /chroot/www/uploads/
+      formDir: /chroot/www/uploads/form/
 
 config:
   # Windows配置(静态资源根目录和代码生成器临时目录)

+ 1 - 0
src/main/resources/application-test.yml

@@ -44,6 +44,7 @@ spring:
 blade:
    file:
       uploadPath: C://chroot/www/uploads/
+      formDir: C://chroot/www/uploads/form/
 
 config:
   # Windows配置(静态资源根目录和代码生成器临时目录)