Commit 33b6d592c77c135e8fcbe835c45fd5de50b98243
1 parent
c495fa5b
fix: fix many to many device generate excel bug
Showing
6 changed files
with
246 additions
and
175 deletions
@@ -34,7 +34,7 @@ public class YtReportGenerateRecordController extends BaseController { | @@ -34,7 +34,7 @@ public class YtReportGenerateRecordController extends BaseController { | ||
34 | @RequestParam(PAGE_SIZE) int pageSize, | 34 | @RequestParam(PAGE_SIZE) int pageSize, |
35 | @RequestParam(PAGE) int page, | 35 | @RequestParam(PAGE) int page, |
36 | @RequestParam(value = "reportConfigName", required = false) String reportConfigName, | 36 | @RequestParam(value = "reportConfigName", required = false) String reportConfigName, |
37 | - @RequestParam(value = "status", required = false) Integer status, | 37 | + @RequestParam(value = "executeStatus", required = false) Integer executeStatus, |
38 | @RequestParam(value = ORDER_FILED, required = false) String orderBy, | 38 | @RequestParam(value = ORDER_FILED, required = false) String orderBy, |
39 | @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) | 39 | @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) |
40 | throws ThingsboardException { | 40 | throws ThingsboardException { |
@@ -44,7 +44,7 @@ public class YtReportGenerateRecordController extends BaseController { | @@ -44,7 +44,7 @@ public class YtReportGenerateRecordController extends BaseController { | ||
44 | queryMap.put(PAGE, page); | 44 | queryMap.put(PAGE, page); |
45 | queryMap.put(ORDER_FILED, orderBy); | 45 | queryMap.put(ORDER_FILED, orderBy); |
46 | queryMap.put("reportConfigName", reportConfigName); | 46 | queryMap.put("reportConfigName", reportConfigName); |
47 | - queryMap.put("status", status); | 47 | + queryMap.put("executeStatus", executeStatus); |
48 | queryMap.put("tenantId", getCurrentUser().getCurrentTenantId()); | 48 | queryMap.put("tenantId", getCurrentUser().getCurrentTenantId()); |
49 | if (orderType != null) { | 49 | if (orderType != null) { |
50 | queryMap.put(ORDER_TYPE, orderType.name()); | 50 | queryMap.put(ORDER_TYPE, orderType.name()); |
1 | package org.thingsboard.server.common.data.yunteng.utils; | 1 | package org.thingsboard.server.common.data.yunteng.utils; |
2 | 2 | ||
3 | import com.alibaba.excel.EasyExcel; | 3 | import com.alibaba.excel.EasyExcel; |
4 | +import com.alibaba.excel.ExcelWriter; | ||
5 | +import com.alibaba.excel.write.metadata.WriteSheet; | ||
4 | import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; | 6 | import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; |
5 | import org.apache.commons.lang3.StringUtils; | 7 | import org.apache.commons.lang3.StringUtils; |
6 | import org.springframework.beans.BeanUtils; | 8 | import org.springframework.beans.BeanUtils; |
@@ -17,6 +19,7 @@ import java.util.List; | @@ -17,6 +19,7 @@ import java.util.List; | ||
17 | 19 | ||
18 | public class ExcelUtil { | 20 | public class ExcelUtil { |
19 | private ExcelUtil() {} | 21 | private ExcelUtil() {} |
22 | + | ||
20 | public static void exportExcel( | 23 | public static void exportExcel( |
21 | HttpServletResponse response, | 24 | HttpServletResponse response, |
22 | String fileName, | 25 | String fileName, |
@@ -54,14 +57,26 @@ public class ExcelUtil { | @@ -54,14 +57,26 @@ public class ExcelUtil { | ||
54 | exportExcel(response, fileName, sheetName, targetList, targetClass); | 57 | exportExcel(response, fileName, sheetName, targetList, targetClass); |
55 | } | 58 | } |
56 | 59 | ||
57 | - public static ByteArrayOutputStream noModelWrite(String fileName, List<List<String>> heads, List<?> data) { | 60 | + public static ByteArrayOutputStream noModelWrite( |
61 | + String fileName, List<List<String>> heads, List<?> data) { | ||
58 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); | 62 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); |
59 | EasyExcel.write(byteArrayOutputStream) | 63 | EasyExcel.write(byteArrayOutputStream) |
60 | - .head(heads) | ||
61 | - .sheet(fileName) | ||
62 | - // 自适应列宽 | ||
63 | - .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) | ||
64 | - .doWrite(data); | 64 | + .head(heads) |
65 | + .sheet(fileName) | ||
66 | + // 自适应列宽 | ||
67 | + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) | ||
68 | + .doWrite(data); | ||
65 | return byteArrayOutputStream; | 69 | return byteArrayOutputStream; |
66 | } | 70 | } |
71 | + | ||
72 | + public static void noModelWrite( | ||
73 | + ExcelWriter excelWriter, | ||
74 | + List<List<String>> heads, | ||
75 | + List<?> data, | ||
76 | + int sheetNo, | ||
77 | + String newSheetName) { | ||
78 | + WriteSheet writeSheet = EasyExcel.writerSheet(sheetNo, newSheetName).head(heads).build(); | ||
79 | + excelWriter.write(data, writeSheet); | ||
80 | + } | ||
81 | + | ||
67 | } | 82 | } |
1 | package org.thingsboard.server.dao.util.yunteng.task; | 1 | package org.thingsboard.server.dao.util.yunteng.task; |
2 | 2 | ||
3 | -import com.google.common.util.concurrent.FutureCallback; | ||
4 | -import com.google.common.util.concurrent.Futures; | ||
5 | -import com.google.common.util.concurrent.MoreExecutors; | ||
6 | import lombok.RequiredArgsConstructor; | 3 | import lombok.RequiredArgsConstructor; |
7 | import lombok.extern.slf4j.Slf4j; | 4 | import lombok.extern.slf4j.Slf4j; |
8 | -import org.jetbrains.annotations.NotNull; | ||
9 | -import org.springframework.http.HttpStatus; | ||
10 | -import org.springframework.http.ResponseEntity; | ||
11 | import org.springframework.stereotype.Component; | 5 | import org.springframework.stereotype.Component; |
12 | -import org.springframework.web.context.request.async.DeferredResult; | ||
13 | -import org.thingsboard.common.util.JacksonUtil; | ||
14 | -import org.thingsboard.server.common.data.id.DeviceId; | ||
15 | -import org.thingsboard.server.common.data.id.TenantId; | ||
16 | -import org.thingsboard.server.common.data.kv.*; | ||
17 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | 6 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
18 | -import org.thingsboard.server.common.data.yunteng.core.utils.FileStorageService; | ||
19 | import org.thingsboard.server.common.data.yunteng.dto.ReportFormConfigDTO; | 7 | import org.thingsboard.server.common.data.yunteng.dto.ReportFormConfigDTO; |
20 | -import org.thingsboard.server.common.data.yunteng.dto.ReportGenerateRecordDTO; | ||
21 | -import org.thingsboard.server.common.data.yunteng.dto.report.BasicData; | ||
22 | import org.thingsboard.server.common.data.yunteng.dto.request.ExecuteAttributesDTO; | 8 | import org.thingsboard.server.common.data.yunteng.dto.request.ExecuteAttributesDTO; |
23 | -import org.thingsboard.server.common.data.yunteng.dto.request.QueryConditionDTO; | ||
24 | -import org.thingsboard.server.common.data.yunteng.enums.StatusEnum; | ||
25 | -import org.thingsboard.server.common.data.yunteng.utils.ExcelUtil; | ||
26 | -import org.thingsboard.server.dao.timeseries.TimeseriesService; | ||
27 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; | 9 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; |
28 | import org.thingsboard.server.dao.yunteng.service.YtReportGenerateRecordService; | 10 | import org.thingsboard.server.dao.yunteng.service.YtReportGenerateRecordService; |
29 | - | ||
30 | -import java.io.ByteArrayInputStream; | ||
31 | -import java.io.ByteArrayOutputStream; | ||
32 | -import java.io.InputStream; | ||
33 | -import java.sql.Timestamp; | ||
34 | -import java.time.format.DateTimeFormatter; | ||
35 | import java.util.*; | 11 | import java.util.*; |
36 | -import java.util.stream.Collectors; | ||
37 | 12 | ||
38 | @Component("reportTask") | 13 | @Component("reportTask") |
39 | @RequiredArgsConstructor | 14 | @RequiredArgsConstructor |
40 | @Slf4j | 15 | @Slf4j |
41 | public class ReportTask { | 16 | public class ReportTask { |
42 | - private static final String CONTENT_TYPE = | ||
43 | - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; | ||
44 | private final YtReportFormConfigService ytReportFormConfigService; | 17 | private final YtReportFormConfigService ytReportFormConfigService; |
45 | - private final TimeseriesService tsService; | ||
46 | - private final FileStorageService fileStorageService; | 18 | + |
47 | private final YtReportGenerateRecordService ytReportGenerateRecordService; | 19 | private final YtReportGenerateRecordService ytReportGenerateRecordService; |
48 | 20 | ||
49 | public void multipleParams(String s, Boolean b, Long l, Double d, Integer i) { | 21 | public void multipleParams(String s, Boolean b, Long l, Double d, Integer i) { |
@@ -68,147 +40,22 @@ public class ReportTask { | @@ -68,147 +40,22 @@ public class ReportTask { | ||
68 | if (dtoList.size() == FastIotConstants.MagicNumber.ONE) { | 40 | if (dtoList.size() == FastIotConstants.MagicNumber.ONE) { |
69 | try { | 41 | try { |
70 | ExecuteAttributesDTO attributesDTO = dtoList.get(0); | 42 | ExecuteAttributesDTO attributesDTO = dtoList.get(0); |
71 | - getTsKv(formConfigDTO, attributesDTO, reportGenerateRecordId); | 43 | + getTsKvForGenerateExcel(formConfigDTO, attributesDTO, reportGenerateRecordId); |
72 | 44 | ||
73 | } catch (Exception e) { | 45 | } catch (Exception e) { |
74 | log.error(e.getMessage()); | 46 | log.error(e.getMessage()); |
75 | } | 47 | } |
76 | } else { | 48 | } else { |
77 | for (ExecuteAttributesDTO dto : dtoList) { | 49 | for (ExecuteAttributesDTO dto : dtoList) { |
78 | - getTsKv(formConfigDTO, dto, reportGenerateRecordId); | 50 | + getTsKvForGenerateExcel(formConfigDTO, dto, reportGenerateRecordId); |
79 | } | 51 | } |
80 | } | 52 | } |
81 | } | 53 | } |
82 | } | 54 | } |
83 | 55 | ||
84 | - private void getTsKv( | 56 | + private void getTsKvForGenerateExcel( |
85 | ReportFormConfigDTO formConfigDTO, ExecuteAttributesDTO dto, String reportGenerateRecordId) { | 57 | ReportFormConfigDTO formConfigDTO, ExecuteAttributesDTO dto, String reportGenerateRecordId) { |
86 | - Long startTs = formConfigDTO.getStartTs(); | ||
87 | - Long endTs = formConfigDTO.getEndTs(); | ||
88 | - QueryConditionDTO queryCondition = formConfigDTO.getQueryCondition(); | ||
89 | - boolean useStrictDataTypes = queryCondition.isUseStrictDataTypes(); | ||
90 | - Long interval = queryCondition.getInterval(); | ||
91 | - int limit = queryCondition.getLimit(); | ||
92 | - Aggregation agg = queryCondition.getAgg(); | ||
93 | - String orderBy = queryCondition.getOrderBy(); | ||
94 | - List<String> keys = dto.getAttributes(); | ||
95 | - DeviceId entityId = DeviceId.fromString(dto.getDevice()); | ||
96 | - String reportTenantId = formConfigDTO.getTenantId(); | ||
97 | - TenantId tenantId = TenantId.fromUUID(UUID.fromString(reportTenantId)); | ||
98 | - final DeferredResult<ResponseEntity> result = new DeferredResult<>(); | ||
99 | - List<ReadTsKvQuery> queries = | ||
100 | - keys.stream() | ||
101 | - .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy)) | ||
102 | - .collect(Collectors.toList()); | ||
103 | - | ||
104 | - Futures.addCallback( | ||
105 | - tsService.findAll(tenantId, entityId, queries), | ||
106 | - getTsKvListCallback( | ||
107 | - result, | ||
108 | - useStrictDataTypes, | ||
109 | - formConfigDTO.getName(), | ||
110 | - dto.getName(), | ||
111 | - reportGenerateRecordId, | ||
112 | - reportTenantId), | ||
113 | - MoreExecutors.directExecutor()); | ||
114 | - } | ||
115 | - | ||
116 | - private FutureCallback<List<TsKvEntry>> getTsKvListCallback( | ||
117 | - final DeferredResult<ResponseEntity> response, | ||
118 | - Boolean useStrictDataTypes, | ||
119 | - String reportFormName, | ||
120 | - String deviceName, | ||
121 | - String reportGenerateRecordId, | ||
122 | - String tenantId) { | ||
123 | - return new FutureCallback<>() { | ||
124 | - @Override | ||
125 | - public void onSuccess(List<TsKvEntry> data) { | ||
126 | - Map<String, List<BasicData>> result = new LinkedHashMap<>(); | ||
127 | - for (TsKvEntry entry : data) { | ||
128 | - Object value = useStrictDataTypes ? getKvValue(entry) : entry.getValueAsString(); | ||
129 | - result | ||
130 | - .computeIfAbsent(entry.getKey(), k -> new ArrayList<>()) | ||
131 | - .add(new BasicData(entry.getTs(), value)); | ||
132 | - } | ||
133 | - response.setResult(new ResponseEntity<>(result, HttpStatus.OK)); | ||
134 | - generateExcel(deviceName, reportFormName, result, reportGenerateRecordId, tenantId); | ||
135 | - } | ||
136 | - | ||
137 | - @Override | ||
138 | - public void onFailure(@NotNull Throwable e) { | ||
139 | - log.error("Failed to fetch historical data", e); | ||
140 | - } | ||
141 | - }; | ||
142 | - } | ||
143 | - | ||
144 | - private Object getKvValue(KvEntry entry) { | ||
145 | - if (entry.getDataType() == DataType.JSON) { | ||
146 | - Optional<String> json = entry.getJsonValue(); | ||
147 | - return json.map(JacksonUtil::toJsonNode).orElse(null); | ||
148 | - } | ||
149 | - return entry.getValue(); | ||
150 | - } | ||
151 | - | ||
152 | - private void generateExcel( | ||
153 | - String deviceName, | ||
154 | - String reportFormName, | ||
155 | - Map<String, List<BasicData>> result, | ||
156 | - String reportGenerateRecordId, | ||
157 | - String tenantId) { | ||
158 | - List<List<String>> heads = new ArrayList<>(); | ||
159 | - List<List<Object>> values = new ArrayList<>(); | ||
160 | - int firstKey = 0; | ||
161 | - for (String key : result.keySet()) { | ||
162 | - List<String> headValue = new ArrayList<>(); | ||
163 | - headValue.add(deviceName); | ||
164 | - headValue.add(key + "采集值"); | ||
165 | - List<String> tsValue = new ArrayList<>(); | ||
166 | - tsValue.add(deviceName); | ||
167 | - tsValue.add(key + "采集时间"); | ||
168 | - heads.add(headValue); | ||
169 | - heads.add(tsValue); | ||
170 | - List<BasicData> basicData = result.get(key); | ||
171 | - for (int i = 0; i < basicData.size(); i++) { | ||
172 | - BasicData item = basicData.get(i); | ||
173 | - List<Object> listValue; | ||
174 | - if (firstKey == 0) { | ||
175 | - listValue = new ArrayList<>(); | ||
176 | - } else { | ||
177 | - listValue = values.get(i); | ||
178 | - } | ||
179 | - Object value = item.getValue(); | ||
180 | - listValue.add(value); | ||
181 | - DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||
182 | - Timestamp t = new Timestamp(item.getTs()); | ||
183 | - listValue.add(t.toLocalDateTime().format(dtf)); | ||
184 | - if (firstKey == 0) { | ||
185 | - values.add(listValue); | ||
186 | - } | ||
187 | - } | ||
188 | - firstKey++; | ||
189 | - } | ||
190 | - ByteArrayOutputStream byteArrayOutputStream = | ||
191 | - ExcelUtil.noModelWrite(reportFormName, heads, values); | ||
192 | - String fileName = reportFormName + System.currentTimeMillis() + ".xlsx"; | ||
193 | - int status = StatusEnum.SUCCESS.getIndex(); | ||
194 | - InputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); | ||
195 | - String response = null; | ||
196 | - try { | ||
197 | - response = fileStorageService.uploadFile(fileName, CONTENT_TYPE, inputStream); | ||
198 | - } catch (Exception e) { | ||
199 | - log.error(e.getMessage()); | ||
200 | - } | ||
201 | - ReportGenerateRecordDTO recordDTO = | ||
202 | - ytReportGenerateRecordService.findReportGenerateRecordById( | ||
203 | - reportGenerateRecordId, tenantId); | ||
204 | - if (null != recordDTO) { | ||
205 | - if (response != null) { | ||
206 | - recordDTO.setReportPath(response); | ||
207 | - } else { | ||
208 | - status = StatusEnum.FAIL.getIndex(); | ||
209 | - } | ||
210 | - recordDTO.setExecuteStatus(status); | ||
211 | - ytReportGenerateRecordService.saveOrUpdateReportGenerateRecord(recordDTO); | ||
212 | - } | 58 | + ytReportGenerateRecordService.generateExcelUpdateReportRecord( |
59 | + formConfigDTO, dto, reportGenerateRecordId); | ||
213 | } | 60 | } |
214 | } | 61 | } |
@@ -24,6 +24,7 @@ import org.thingsboard.server.dao.yunteng.mapper.OrganizationMapper; | @@ -24,6 +24,7 @@ import org.thingsboard.server.dao.yunteng.mapper.OrganizationMapper; | ||
24 | import org.thingsboard.server.dao.yunteng.mapper.ReportFormConfigMapper; | 24 | import org.thingsboard.server.dao.yunteng.mapper.ReportFormConfigMapper; |
25 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | 25 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; |
26 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; | 26 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; |
27 | +import org.thingsboard.server.dao.yunteng.service.YtReportGenerateRecordService; | ||
27 | import org.thingsboard.server.dao.yunteng.service.YtSysJobService; | 28 | import org.thingsboard.server.dao.yunteng.service.YtSysJobService; |
28 | 29 | ||
29 | import java.util.*; | 30 | import java.util.*; |
@@ -37,6 +38,7 @@ public class YtReportFromConfigServiceImpl | @@ -37,6 +38,7 @@ public class YtReportFromConfigServiceImpl | ||
37 | implements YtReportFormConfigService { | 38 | implements YtReportFormConfigService { |
38 | private final OrganizationMapper organizationMapper; | 39 | private final OrganizationMapper organizationMapper; |
39 | private final YtSysJobService ytSysJobService; | 40 | private final YtSysJobService ytSysJobService; |
41 | + private final YtReportGenerateRecordService ytReportGenerateRecordService; | ||
40 | 42 | ||
41 | @Override | 43 | @Override |
42 | public YtPageData<ReportFormConfigDTO> page(Map<String, Object> queryMap) { | 44 | public YtPageData<ReportFormConfigDTO> page(Map<String, Object> queryMap) { |
@@ -161,7 +163,7 @@ public class YtReportFromConfigServiceImpl | @@ -161,7 +163,7 @@ public class YtReportFromConfigServiceImpl | ||
161 | @Override | 163 | @Override |
162 | public ReportFormConfigDTO findReportFormConfigById(String id) { | 164 | public ReportFormConfigDTO findReportFormConfigById(String id) { |
163 | return Optional.ofNullable(baseMapper.selectById(id)) | 165 | return Optional.ofNullable(baseMapper.selectById(id)) |
164 | - .map(obj -> getReportFormConfigDTOByEntity(obj)) | 166 | + .map(this::getReportFormConfigDTOByEntity) |
165 | .orElseThrow( | 167 | .orElseThrow( |
166 | () -> { | 168 | () -> { |
167 | throw new YtDataValidationException(ErrorMessage.INTERNAL_ERROR.getMessage()); | 169 | throw new YtDataValidationException(ErrorMessage.INTERNAL_ERROR.getMessage()); |
@@ -174,6 +176,8 @@ public class YtReportFromConfigServiceImpl | @@ -174,6 +176,8 @@ public class YtReportFromConfigServiceImpl | ||
174 | if (isNowExecute) { | 176 | if (isNowExecute) { |
175 | if (Objects.equals(reportFormConfig.getStatus(), StatusEnum.ENABLE.getIndex())) { | 177 | if (Objects.equals(reportFormConfig.getStatus(), StatusEnum.ENABLE.getIndex())) { |
176 | // 立即执行报表生成,并创建一条报表执行记录 | 178 | // 立即执行报表生成,并创建一条报表执行记录 |
179 | + // 生成Excel,并更新报表执行记录 | ||
180 | + generateExcelUpdateReportRecord(reportFormConfig.getDTO(ReportFormConfigDTO.class)); | ||
177 | } | 181 | } |
178 | } else { | 182 | } else { |
179 | createSysJob(reportFormConfig); | 183 | createSysJob(reportFormConfig); |
@@ -195,7 +199,8 @@ public class YtReportFromConfigServiceImpl | @@ -195,7 +199,8 @@ public class YtReportFromConfigServiceImpl | ||
195 | ytSysJobService.deleteJob(sysJobDTO); | 199 | ytSysJobService.deleteJob(sysJobDTO); |
196 | if (enableStatus) { | 200 | if (enableStatus) { |
197 | // 立即执行报表生成,并创建一条报表执行记录 | 201 | // 立即执行报表生成,并创建一条报表执行记录 |
198 | - // TODO hxp | 202 | + // 生成Excel,并更新报表执行记录 |
203 | + generateExcelUpdateReportRecord(reportFormConfig.getDTO(ReportFormConfigDTO.class)); | ||
199 | } | 204 | } |
200 | } else { | 205 | } else { |
201 | // 修改cron表达式 | 206 | // 修改cron表达式 |
@@ -207,7 +212,8 @@ public class YtReportFromConfigServiceImpl | @@ -207,7 +212,8 @@ public class YtReportFromConfigServiceImpl | ||
207 | if (isNowExecute) { | 212 | if (isNowExecute) { |
208 | if (enableStatus) { | 213 | if (enableStatus) { |
209 | // 立即执行报表生成,并创建一条报表执行记录 | 214 | // 立即执行报表生成,并创建一条报表执行记录 |
210 | - // TODO hxp | 215 | + // 生成Excel,并更新报表执行记录 |
216 | + generateExcelUpdateReportRecord(reportFormConfig.getDTO(ReportFormConfigDTO.class)); | ||
211 | } | 217 | } |
212 | } else { | 218 | } else { |
213 | createSysJob(reportFormConfig); | 219 | createSysJob(reportFormConfig); |
@@ -272,6 +278,7 @@ public class YtReportFromConfigServiceImpl | @@ -272,6 +278,7 @@ public class YtReportFromConfigServiceImpl | ||
272 | for (JsonNode key : attribute) { | 278 | for (JsonNode key : attribute) { |
273 | attributes.add(JacksonUtil.convertValue(key, String.class)); | 279 | attributes.add(JacksonUtil.convertValue(key, String.class)); |
274 | } | 280 | } |
281 | + assert attributesDTO != null; | ||
275 | attributesDTO.setAttributes(attributes); | 282 | attributesDTO.setAttributes(attributes); |
276 | list.add(attributesDTO); | 283 | list.add(attributesDTO); |
277 | } | 284 | } |
@@ -281,4 +288,19 @@ public class YtReportFromConfigServiceImpl | @@ -281,4 +288,19 @@ public class YtReportFromConfigServiceImpl | ||
281 | dto.setQueryCondition(queryCondition); | 288 | dto.setQueryCondition(queryCondition); |
282 | return dto; | 289 | return dto; |
283 | } | 290 | } |
291 | + | ||
292 | + private void generateExcelUpdateReportRecord(ReportFormConfigDTO reportFormConfigDTO) { | ||
293 | + if (reportFormConfigDTO == null) { | ||
294 | + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()); | ||
295 | + } | ||
296 | + // 立即执行报表生成,并创建一条报表执行记录 | ||
297 | + ReportGenerateRecordDTO recordDTO = | ||
298 | + ytReportGenerateRecordService.generateReportRecord( | ||
299 | + reportFormConfigDTO.getId(), reportFormConfigDTO.getTenantId(), null); | ||
300 | + // 生成Excel,并更新报表执行记录 | ||
301 | + for (ExecuteAttributesDTO dto : reportFormConfigDTO.getExecuteAttributes()) { | ||
302 | + ytReportGenerateRecordService.generateExcelUpdateReportRecord( | ||
303 | + reportFormConfigDTO, dto, recordDTO.getId()); | ||
304 | + } | ||
305 | + } | ||
284 | } | 306 | } |
@@ -2,18 +2,36 @@ package org.thingsboard.server.dao.yunteng.impl; | @@ -2,18 +2,36 @@ package org.thingsboard.server.dao.yunteng.impl; | ||
2 | 2 | ||
3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
4 | import com.baomidou.mybatisplus.core.metadata.IPage; | 4 | import com.baomidou.mybatisplus.core.metadata.IPage; |
5 | +import com.google.common.util.concurrent.FutureCallback; | ||
6 | +import com.google.common.util.concurrent.Futures; | ||
7 | +import com.google.common.util.concurrent.MoreExecutors; | ||
5 | import lombok.RequiredArgsConstructor; | 8 | import lombok.RequiredArgsConstructor; |
9 | +import lombok.extern.slf4j.Slf4j; | ||
6 | import org.apache.commons.lang3.StringUtils; | 10 | import org.apache.commons.lang3.StringUtils; |
11 | +import org.jetbrains.annotations.NotNull; | ||
12 | +import org.springframework.http.HttpStatus; | ||
13 | +import org.springframework.http.ResponseEntity; | ||
7 | import org.springframework.stereotype.Service; | 14 | import org.springframework.stereotype.Service; |
15 | +import org.springframework.web.context.request.async.DeferredResult; | ||
16 | +import org.thingsboard.common.util.JacksonUtil; | ||
17 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
18 | +import org.thingsboard.server.common.data.id.TenantId; | ||
19 | +import org.thingsboard.server.common.data.kv.*; | ||
8 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | 20 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
9 | import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; | 21 | import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; |
10 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; | 22 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
23 | +import org.thingsboard.server.common.data.yunteng.core.utils.FileStorageService; | ||
11 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; | 24 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; |
12 | import org.thingsboard.server.common.data.yunteng.dto.OrganizationDTO; | 25 | import org.thingsboard.server.common.data.yunteng.dto.OrganizationDTO; |
13 | import org.thingsboard.server.common.data.yunteng.dto.ReportFormConfigDTO; | 26 | import org.thingsboard.server.common.data.yunteng.dto.ReportFormConfigDTO; |
14 | import org.thingsboard.server.common.data.yunteng.dto.ReportGenerateRecordDTO; | 27 | import org.thingsboard.server.common.data.yunteng.dto.ReportGenerateRecordDTO; |
28 | +import org.thingsboard.server.common.data.yunteng.dto.report.BasicData; | ||
29 | +import org.thingsboard.server.common.data.yunteng.dto.request.ExecuteAttributesDTO; | ||
30 | +import org.thingsboard.server.common.data.yunteng.dto.request.QueryConditionDTO; | ||
15 | import org.thingsboard.server.common.data.yunteng.enums.StatusEnum; | 31 | import org.thingsboard.server.common.data.yunteng.enums.StatusEnum; |
32 | +import org.thingsboard.server.common.data.yunteng.utils.ExcelUtil; | ||
16 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | 33 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
34 | +import org.thingsboard.server.dao.timeseries.TimeseriesService; | ||
17 | import org.thingsboard.server.dao.yunteng.entities.ReportGenerateRecord; | 35 | import org.thingsboard.server.dao.yunteng.entities.ReportGenerateRecord; |
18 | import org.thingsboard.server.dao.yunteng.mapper.ReportGenerateRecordMapper; | 36 | import org.thingsboard.server.dao.yunteng.mapper.ReportGenerateRecordMapper; |
19 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | 37 | import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; |
@@ -21,18 +39,29 @@ import org.thingsboard.server.dao.yunteng.service.YtOrganizationService; | @@ -21,18 +39,29 @@ import org.thingsboard.server.dao.yunteng.service.YtOrganizationService; | ||
21 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; | 39 | import org.thingsboard.server.dao.yunteng.service.YtReportFormConfigService; |
22 | import org.thingsboard.server.dao.yunteng.service.YtReportGenerateRecordService; | 40 | import org.thingsboard.server.dao.yunteng.service.YtReportGenerateRecordService; |
23 | 41 | ||
42 | +import java.io.ByteArrayInputStream; | ||
43 | +import java.io.ByteArrayOutputStream; | ||
44 | +import java.io.InputStream; | ||
45 | +import java.sql.Timestamp; | ||
24 | import java.time.LocalDateTime; | 46 | import java.time.LocalDateTime; |
25 | -import java.util.Map; | ||
26 | -import java.util.Optional; | 47 | +import java.time.ZoneOffset; |
48 | +import java.time.format.DateTimeFormatter; | ||
49 | +import java.util.*; | ||
50 | +import java.util.stream.Collectors; | ||
27 | 51 | ||
28 | @Service | 52 | @Service |
29 | @RequiredArgsConstructor | 53 | @RequiredArgsConstructor |
54 | +@Slf4j | ||
30 | public class YtReportGenerateRecordServiceImpl | 55 | public class YtReportGenerateRecordServiceImpl |
31 | extends AbstractBaseService<ReportGenerateRecordMapper, ReportGenerateRecord> | 56 | extends AbstractBaseService<ReportGenerateRecordMapper, ReportGenerateRecord> |
32 | implements YtReportGenerateRecordService { | 57 | implements YtReportGenerateRecordService { |
33 | 58 | ||
59 | + private static final String CONTENT_TYPE = | ||
60 | + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; | ||
34 | private final YtReportFormConfigService ytReportFormConfigService; | 61 | private final YtReportFormConfigService ytReportFormConfigService; |
35 | private final YtOrganizationService ytOrganizationService; | 62 | private final YtOrganizationService ytOrganizationService; |
63 | + private final TimeseriesService tsService; | ||
64 | + private final FileStorageService fileStorageService; | ||
36 | 65 | ||
37 | @Override | 66 | @Override |
38 | public YtPageData<ReportGenerateRecordDTO> page(Map<String, Object> queryMap) { | 67 | public YtPageData<ReportGenerateRecordDTO> page(Map<String, Object> queryMap) { |
@@ -42,8 +71,8 @@ public class YtReportGenerateRecordServiceImpl | @@ -42,8 +71,8 @@ public class YtReportGenerateRecordServiceImpl | ||
42 | (LocalDateTime) Optional.ofNullable(queryMap.get("startTime")).orElse(null); | 71 | (LocalDateTime) Optional.ofNullable(queryMap.get("startTime")).orElse(null); |
43 | LocalDateTime endTime = | 72 | LocalDateTime endTime = |
44 | (LocalDateTime) Optional.ofNullable(queryMap.get("endTime")).orElse(null); | 73 | (LocalDateTime) Optional.ofNullable(queryMap.get("endTime")).orElse(null); |
45 | - Integer status = | ||
46 | - Optional.ofNullable(queryMap.get("status")) | 74 | + Integer executeStatus = |
75 | + Optional.ofNullable(queryMap.get("executeStatus")) | ||
47 | .map(obj -> Integer.valueOf(obj.toString())) | 76 | .map(obj -> Integer.valueOf(obj.toString())) |
48 | .orElse(null); | 77 | .orElse(null); |
49 | IPage<ReportGenerateRecord> iPage = | 78 | IPage<ReportGenerateRecord> iPage = |
@@ -54,7 +83,7 @@ public class YtReportGenerateRecordServiceImpl | @@ -54,7 +83,7 @@ public class YtReportGenerateRecordServiceImpl | ||
54 | StringUtils.isNotEmpty(reportConfigName), | 83 | StringUtils.isNotEmpty(reportConfigName), |
55 | ReportGenerateRecord::getReportConfigName, | 84 | ReportGenerateRecord::getReportConfigName, |
56 | reportConfigName) | 85 | reportConfigName) |
57 | - .eq(null != status, ReportGenerateRecord::getExecuteStatus, status) | 86 | + .eq(null != executeStatus, ReportGenerateRecord::getExecuteStatus, executeStatus) |
58 | .eq(ReportGenerateRecord::getTenantId, queryMap.get("tenantId").toString()) | 87 | .eq(ReportGenerateRecord::getTenantId, queryMap.get("tenantId").toString()) |
59 | .between( | 88 | .between( |
60 | null != startTime && null != endTime, | 89 | null != startTime && null != endTime, |
@@ -126,4 +155,152 @@ public class YtReportGenerateRecordServiceImpl | @@ -126,4 +155,152 @@ public class YtReportGenerateRecordServiceImpl | ||
126 | dto.setExecuteWay(reportFormConfigDTO.getExecuteWay()); | 155 | dto.setExecuteWay(reportFormConfigDTO.getExecuteWay()); |
127 | return saveOrUpdateReportGenerateRecord(dto); | 156 | return saveOrUpdateReportGenerateRecord(dto); |
128 | } | 157 | } |
158 | + | ||
159 | + @Override | ||
160 | + public void generateExcelUpdateReportRecord( | ||
161 | + ReportFormConfigDTO formConfigDTO, ExecuteAttributesDTO dto, String recordId) { | ||
162 | + Long startTs = formConfigDTO.getStartTs(); | ||
163 | + Long endTs = formConfigDTO.getEndTs(); | ||
164 | + QueryConditionDTO queryCondition = formConfigDTO.getQueryCondition(); | ||
165 | + boolean useStrictDataTypes = queryCondition.isUseStrictDataTypes(); | ||
166 | + Long interval = queryCondition.getInterval(); | ||
167 | + int limit = queryCondition.getLimit(); | ||
168 | + Aggregation agg = queryCondition.getAgg(); | ||
169 | + String orderBy = queryCondition.getOrderBy(); | ||
170 | + List<String> keys = dto.getAttributes(); | ||
171 | + DeviceId entityId = DeviceId.fromString(dto.getDevice()); | ||
172 | + String reportTenantId = formConfigDTO.getTenantId(); | ||
173 | + TenantId tenantId = TenantId.fromUUID(UUID.fromString(reportTenantId)); | ||
174 | + final DeferredResult<ResponseEntity> result = new DeferredResult<>(); | ||
175 | + // 如果报表配置是定时执行,获取当前定时任务的执行时间,并计算新的开始时间、结束时间 | ||
176 | + if (formConfigDTO.getExecuteWay() == FastIotConstants.MagicNumber.ONE) { | ||
177 | + long differenceTs = endTs - startTs; | ||
178 | + endTs = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); | ||
179 | + startTs = endTs - differenceTs; | ||
180 | + } | ||
181 | + Long finalStartTs = startTs; | ||
182 | + Long finalEndTs = endTs; | ||
183 | + List<ReadTsKvQuery> queries = | ||
184 | + keys.stream() | ||
185 | + .map( | ||
186 | + key -> | ||
187 | + new BaseReadTsKvQuery( | ||
188 | + key, finalStartTs, finalEndTs, interval, limit, agg, orderBy)) | ||
189 | + .collect(Collectors.toList()); | ||
190 | + | ||
191 | + Futures.addCallback( | ||
192 | + tsService.findAll(tenantId, entityId, queries), | ||
193 | + getTsKvListCallback( | ||
194 | + result, | ||
195 | + useStrictDataTypes, | ||
196 | + formConfigDTO.getName(), | ||
197 | + dto.getName(), | ||
198 | + recordId, | ||
199 | + reportTenantId), | ||
200 | + MoreExecutors.directExecutor()); | ||
201 | + } | ||
202 | + | ||
203 | + private FutureCallback<List<TsKvEntry>> getTsKvListCallback( | ||
204 | + final DeferredResult<ResponseEntity> response, | ||
205 | + Boolean useStrictDataTypes, | ||
206 | + String reportFormName, | ||
207 | + String deviceName, | ||
208 | + String reportGenerateRecordId, | ||
209 | + String tenantId) { | ||
210 | + return new FutureCallback<>() { | ||
211 | + @Override | ||
212 | + public void onSuccess(List<TsKvEntry> data) { | ||
213 | + Map<String, List<BasicData>> result = new LinkedHashMap<>(); | ||
214 | + for (TsKvEntry entry : data) { | ||
215 | + Object value = useStrictDataTypes ? getKvValue(entry) : entry.getValueAsString(); | ||
216 | + result | ||
217 | + .computeIfAbsent(entry.getKey(), k -> new ArrayList<>()) | ||
218 | + .add(new BasicData(entry.getTs(), value)); | ||
219 | + } | ||
220 | + response.setResult(new ResponseEntity<>(result, HttpStatus.OK)); | ||
221 | + generateExcel(deviceName, reportFormName, result, reportGenerateRecordId, tenantId); | ||
222 | + } | ||
223 | + | ||
224 | + @Override | ||
225 | + public void onFailure(@NotNull Throwable e) { | ||
226 | + log.error("Failed to fetch historical data", e); | ||
227 | + } | ||
228 | + }; | ||
229 | + } | ||
230 | + | ||
231 | + private Object getKvValue(KvEntry entry) { | ||
232 | + if (entry.getDataType() == DataType.JSON) { | ||
233 | + Optional<String> json = entry.getJsonValue(); | ||
234 | + return json.map(JacksonUtil::toJsonNode).orElse(null); | ||
235 | + } | ||
236 | + return entry.getValue(); | ||
237 | + } | ||
238 | + | ||
239 | + private void generateExcel( | ||
240 | + String deviceName, | ||
241 | + String reportFormName, | ||
242 | + Map<String, List<BasicData>> result, | ||
243 | + String reportGenerateRecordId, | ||
244 | + String tenantId) { | ||
245 | + List<List<String>> heads = new ArrayList<>(); | ||
246 | + List<List<Object>> values = new ArrayList<>(); | ||
247 | + int firstKey = 0; | ||
248 | + for (String key : result.keySet()) { | ||
249 | + List<String> headValue = new ArrayList<>(); | ||
250 | + headValue.add(deviceName); | ||
251 | + headValue.add(key + "采集值"); | ||
252 | + List<String> tsValue = new ArrayList<>(); | ||
253 | + tsValue.add(deviceName); | ||
254 | + tsValue.add(key + "采集时间"); | ||
255 | + heads.add(headValue); | ||
256 | + heads.add(tsValue); | ||
257 | + List<BasicData> basicData = result.get(key); | ||
258 | + for (int i = 0; i < basicData.size(); i++) { | ||
259 | + BasicData item = basicData.get(i); | ||
260 | + List<Object> listValue; | ||
261 | + if (firstKey == 0) { | ||
262 | + listValue = new ArrayList<>(); | ||
263 | + } else { | ||
264 | + listValue = values.get(i); | ||
265 | + } | ||
266 | + Object value = item.getValue(); | ||
267 | + listValue.add(value); | ||
268 | + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||
269 | + Timestamp t = new Timestamp(item.getTs()); | ||
270 | + listValue.add(t.toLocalDateTime().format(dtf)); | ||
271 | + if (firstKey == 0) { | ||
272 | + values.add(listValue); | ||
273 | + } | ||
274 | + } | ||
275 | + firstKey++; | ||
276 | + } | ||
277 | + ByteArrayOutputStream byteArrayOutputStream = | ||
278 | + ExcelUtil.noModelWrite(reportFormName, heads, values); | ||
279 | + String fileName = reportFormName + System.currentTimeMillis() + ".xlsx"; | ||
280 | + int status = StatusEnum.SUCCESS.getIndex(); | ||
281 | + InputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); | ||
282 | + String response = null; | ||
283 | + try { | ||
284 | + response = fileStorageService.uploadFile(fileName, CONTENT_TYPE, inputStream); | ||
285 | + } catch (Exception e) { | ||
286 | + log.error(e.getMessage()); | ||
287 | + } | ||
288 | + ReportGenerateRecordDTO recordDTO = | ||
289 | + findReportGenerateRecordById(reportGenerateRecordId, tenantId); | ||
290 | + if (null != recordDTO) { | ||
291 | + if (response != null) { | ||
292 | + String reportPath = recordDTO.getReportPath(); | ||
293 | + if (null != reportPath) { | ||
294 | + reportPath += "," + response; | ||
295 | + } else { | ||
296 | + reportPath = response; | ||
297 | + } | ||
298 | + recordDTO.setReportPath(reportPath); | ||
299 | + } else { | ||
300 | + status = StatusEnum.FAIL.getIndex(); | ||
301 | + } | ||
302 | + recordDTO.setExecuteStatus(status); | ||
303 | + saveOrUpdateReportGenerateRecord(recordDTO); | ||
304 | + } | ||
305 | + } | ||
129 | } | 306 | } |
1 | package org.thingsboard.server.dao.yunteng.service; | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | 2 | ||
3 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; | 3 | import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; |
4 | +import org.thingsboard.server.common.data.yunteng.dto.ReportFormConfigDTO; | ||
4 | import org.thingsboard.server.common.data.yunteng.dto.ReportGenerateRecordDTO; | 5 | import org.thingsboard.server.common.data.yunteng.dto.ReportGenerateRecordDTO; |
6 | +import org.thingsboard.server.common.data.yunteng.dto.request.ExecuteAttributesDTO; | ||
5 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | 7 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
6 | 8 | ||
7 | import java.util.Map; | 9 | import java.util.Map; |
@@ -25,4 +27,12 @@ public interface YtReportGenerateRecordService { | @@ -25,4 +27,12 @@ public interface YtReportGenerateRecordService { | ||
25 | * @return 报表生成记录 | 27 | * @return 报表生成记录 |
26 | */ | 28 | */ |
27 | ReportGenerateRecordDTO generateReportRecord(String sourceId, String tenantId, String jobId); | 29 | ReportGenerateRecordDTO generateReportRecord(String sourceId, String tenantId, String jobId); |
30 | + | ||
31 | + /** | ||
32 | + * 生成报表记录 | ||
33 | + * @param formConfigDTO 报表配置 | ||
34 | + * @param dto 执行属性 | ||
35 | + * @param recordId recordId 记录ID | ||
36 | + */ | ||
37 | + void generateExcelUpdateReportRecord(ReportFormConfigDTO formConfigDTO, ExecuteAttributesDTO dto, String recordId); | ||
28 | } | 38 | } |