diff --git a/backend/framework/pom.xml b/backend/framework/pom.xml index 8d1b25f..aaedccc 100644 --- a/backend/framework/pom.xml +++ b/backend/framework/pom.xml @@ -52,6 +52,11 @@ <artifactId>bcpkix-jdk15on</artifactId> <version>1.68</version> </dependency> + <dependency> + <groupId>org.dhatim</groupId> + <artifactId>fastexcel</artifactId> + <version>0.18.4</version> + </dependency> </dependencies> diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java index f2d5e34..be4eb21 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java @@ -4,26 +4,18 @@ import lombok.EqualsAndHashCode; import org.jumpserver.chen.framework.console.action.DataViewAction; import org.jumpserver.chen.framework.console.component.Logger; +import org.jumpserver.chen.framework.console.dataview.export.DataExport; import org.jumpserver.chen.framework.console.entity.response.SQLResult; import org.jumpserver.chen.framework.console.state.DataViewState; import org.jumpserver.chen.framework.console.state.StateManager; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.SQLQueryParams; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.session.SessionManager; -import org.jumpserver.chen.framework.utils.CodeUtils; import org.jumpserver.chen.framework.ws.io.PacketIO; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.sql.Clob; +import java.io.File; import java.sql.SQLException; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,7 +66,10 @@ public void doAction(DataViewAction action) throws SQLException { this.changeLimit((int) action.getData()); } case DataViewAction.ACTION_EXPORT -> { - this.export((String) action.getData()); + var data = (Map<String, String>) action.getData(); + var scope = data.get("scope"); + var format = data.get("format"); + this.export(scope, format); } } } @@ -134,84 +129,39 @@ private void fullDataViewData(DataViewData viewData, SQLQueryResult result) { } - private static void writeString(BufferedWriter writer, Object object) throws IOException { - var str = object.toString(); - - if (str.contains(",")) { - str = "\"" + str + "\""; - } - writer.write(str); - } - - private void writeCSVData(BufferedWriter writer, DataViewData viewData) throws IOException, SQLException { - - for (Field field : viewData.getFields()) { - writeString(writer, field.getName()); - writer.write(","); - } - for (Map<String, Object> row : viewData.getData()) { - for (Field field : viewData.getFields()) { - var obj = row.get(field.getName()); - if (obj == null) { - writer.write("NULL"); - writer.write(","); - } else if (obj instanceof Clob clob) { - writer.write(CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length()))); - writer.write(","); - } else if (obj instanceof Date) { - SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - writeString(writer, fmt.format(obj)); - } else { - writeString(writer, row.get(field.getName())); - writer.write(","); - } - } - writer.newLine(); - } - - writer.newLine(); - } - - public void export(String scope) throws SQLException { + public void export(String scope, String format) throws SQLException { var session = SessionManager.getCurrentSession(); - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); - String timestamp = LocalDateTime.now().format(formatter); - var f = session.createFile(String.format("data_%s.csv", timestamp)); - CommandRecord command = new CommandRecord(String.format("Export data: %s", this.title)); try { if (!SessionManager.getCurrentSession().canDownload()) { - session.getController().sendFile(f.getName()); return; } - var writer = Files.newBufferedWriter(f.toPath()); - - if (scope.equals("current")) { - this.writeCSVData(writer, this.data); - command.setOutput(String.format("%d rows exported", this.data.getData().size())); - } - - if (scope.equals("all")) { - SQLQueryParams queryParams = new SQLQueryParams(); - queryParams.setLimit(-1); - var result = this.loadDataInterface.loadData(queryParams); - var viewData = new DataViewData(); - this.fullDataViewData(viewData, result); - command.setOutput(String.format("%d rows exported", result.getData().size())); + File f = null; + switch (scope) { + case "current": + f = DataExport.export(format, this.data); + command.setOutput(String.format("%d rows exported", this.data.getData().size())); + break; + case "all": + SQLQueryParams queryParams = new SQLQueryParams(); + queryParams.setLimit(-1); + var result = this.loadDataInterface.loadData(queryParams); + var viewData = new DataViewData(); + this.fullDataViewData(viewData, result); + f = DataExport.export(format, viewData); + command.setOutput(String.format("%d rows exported", result.getData().size())); + break; } - writer.flush(); - writer.close(); this.consoleLogger.success(command.getOutput()); session.recordCommand(command); + session.getController().sendFile(f.getName()); - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException(e); } - session.getController().sendFile(f.getName()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java new file mode 100644 index 0000000..ef71526 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExport.java @@ -0,0 +1,137 @@ +package org.jumpserver.chen.framework.console.dataview.export; + +import org.dhatim.fastexcel.Workbook; +import org.dhatim.fastexcel.Worksheet; +import org.jumpserver.chen.framework.console.dataview.DataViewData; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.utils.CodeUtils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Clob; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.List; +import java.util.Map; + + +public class DataExport { + public static File export(String format, DataViewData data) throws Exception { + var session = SessionManager.getCurrentSession(); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + String timestamp = LocalDateTime.now().format(formatter); + + String filename = String.format("data_%s", timestamp); + + File f; + + switch (format) { + case "excel": + f = session.createFile(String.format("%s.xlsx", filename)); + new DataExportExcel().exportData(f.toPath().toString(), data); + break; + case "csv": + f = session.createFile(String.format("%s.csv", filename)); + new DataExportCSV().exportData(f.toPath().toString(), data); + break; + default: + throw new Exception("unsupported format: " + format); + } + return f; + } +} + + +class DataExportExcel implements DataExportInterface { + @Override + public void exportData(String path, DataViewData data) throws Exception { + try (FileOutputStream fos = new FileOutputStream(path); + Workbook workbook = new Workbook(fos, "JumpServer", "4.0")) { + + Worksheet sheet = workbook.newWorksheet("Data"); + List<Field> fields = data.getFields(); + List<Map<String, Object>> rows = data.getData(); + + for (int col = 0; col < fields.size(); col++) { + sheet.value(0, col, fields.get(col).getName()); + } + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { + Map<String, Object> row = rows.get(rowIndex); + + for (int col = 0; col < fields.size(); col++) { + Field field = fields.get(col); + Object obj = row.get(field.getName()); + + if (obj == null) { + sheet.value(rowIndex + 1, col, "NULL"); + } else if (obj instanceof Clob clob) { + try { + sheet.value(rowIndex + 1, col, clob.getSubString(1, (int) clob.length())); + } catch (Exception e) { + sheet.value(rowIndex + 1, col, "ERROR_CLOB"); + } + } else if (obj instanceof Date) { + sheet.value(rowIndex + 1, col, dateFormat.format(obj)); + } else { + sheet.value(rowIndex + 1, col, obj.toString()); + } + } + } + } + } +} + +class DataExportCSV implements DataExportInterface { + @Override + public void exportData(String path, DataViewData data) throws Exception { + var writer = Files.newBufferedWriter(Path.of(path)); + + for (Field field : data.getFields()) { + writeString(writer, field.getName()); + writer.write(","); + } + for (Map<String, Object> row : data.getData()) { + for (Field field : data.getFields()) { + var obj = row.get(field.getName()); + if (obj == null) { + writer.write("NULL"); + writer.write(","); + } else if (obj instanceof Clob clob) { + writer.write(CodeUtils.escapeCsvValue(clob.getSubString(1, (int) clob.length()))); + writer.write(","); + } else if (obj instanceof Date) { + SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + writeString(writer, fmt.format(obj)); + } else { + writeString(writer, row.get(field.getName())); + writer.write(","); + } + } + writer.newLine(); + } + + writer.newLine(); + writer.flush(); + writer.close(); + } + + private static void writeString(BufferedWriter writer, Object object) throws IOException { + var str = object.toString(); + + if (str.contains(",")) { + str = "\"" + str + "\""; + } + writer.write(str); + } +} \ No newline at end of file diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java new file mode 100644 index 0000000..2bfb075 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/export/DataExportInterface.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.dataview.export; + +import org.jumpserver.chen.framework.console.dataview.DataViewData; + +public interface DataExportInterface { + void exportData(String path, DataViewData data) throws Exception; +} \ No newline at end of file diff --git a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue b/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue index c4029e4..0371bc7 100644 --- a/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue +++ b/frontend/src/components/Main/Explore/DataView/ExportDataDialog.vue @@ -6,8 +6,16 @@ width="40%" > <el-form ref="form" :model="form" label-width="80px"> - <el-radio v-model="form.scope" label="current">{{ $tc('ExportCurrent') }}</el-radio> - <el-radio v-model="form.scope" label="all">{{ $tc('ExportAll') }}</el-radio> + + <el-form-item :label="$tc('Scope')"> + <el-radio v-model="form.scope" label="current">{{ $tc('ExportCurrent') }}</el-radio> + <el-radio v-model="form.scope" label="all">{{ $tc('ExportAll') }}</el-radio> + </el-form-item> + + <el-form-item :label="$tc('Format')"> + <el-radio v-model="form.format" label="csv">CSV</el-radio> + <el-radio v-model="form.format" label="excel">Excel</el-radio> + </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> @@ -35,7 +43,8 @@ export default { data() { return { form: { - scope: 'current' + scope: 'current', + format: 'csv' } } }, @@ -53,7 +62,7 @@ export default { }, methods: { onSubmit() { - this.$emit('submit', this.form.scope) + this.$emit('submit', { scope: this.form.scope, format: this.form.format }) } } }