chore: sync rsf-server from isolated worktree
| | |
| | | String reportDate = getMetaValue(metaMap, "reportDate", ""); |
| | | String printedAt = getMetaValue(metaMap, "printedAt", ""); |
| | | String operator = getMetaValue(metaMap, "operator", ""); |
| | | return new ExcelUtil.ExportMeta(reportTitle, reportDate, printedAt, operator, count); |
| | | return new ExcelUtil.ExportMeta( |
| | | reportTitle, |
| | | reportDate, |
| | | printedAt, |
| | | operator, |
| | | count, |
| | | getReportStyle(metaMap) |
| | | ); |
| | | } |
| | | |
| | | private String getMetaValue(Map<?, ?> metaMap, String key, String defaultValue) { |
| | | Object value = metaMap.get(key); |
| | | return Objects.isNull(value) ? defaultValue : String.valueOf(value); |
| | | } |
| | | |
| | | private Map<String, Object> getReportStyle(Map<?, ?> metaMap) { |
| | | Object reportStyleObject = metaMap.get("reportStyle"); |
| | | if (!(reportStyleObject instanceof Map<?, ?> reportStyleMap)) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | Map<String, Object> reportStyle = new HashMap<>(); |
| | | for (Map.Entry<?, ?> entry : reportStyleMap.entrySet()) { |
| | | reportStyle.put(String.valueOf(entry.getKey()), entry.getValue()); |
| | | } |
| | | return reportStyle; |
| | | } |
| | | } |
| | |
| | | |
| | | import java.util.Date; |
| | | import java.util.Set; |
| | | import java.util.function.Consumer; |
| | | import java.util.function.Function; |
| | | |
| | | /** |
| | | * redis tools |
| | |
| | | return null; |
| | | } |
| | | |
| | | private <T> T withJedis(Function<Jedis, T> action) { |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try (jedis) { |
| | | return action.apply(jedis); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void withJedisVoid(Consumer<Jedis> action) { |
| | | Jedis jedis = this.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try (jedis) { |
| | | action.accept(jedis); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | // key - object ---------------------------------------------------------------------------------------------------------- |
| | | |
| | | public String set(String flag, String key, Object value) { |
| | |
| | | this.delete(flag, key); |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.set((flag + LINK + key).getBytes(), Serialize.serialize(value))); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.set((flag + LINK + key).getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public String set(String flag, String key, Object value, Integer seconds) { |
| | |
| | | this.delete(flag, key); |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.setex((flag + LINK + key).getBytes(), seconds, Serialize.serialize(value))); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.setex((flag + LINK + key).getBytes(), seconds, Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public <T> T get(String flag, String key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | byte[] bytes = jedis.get((flag + LINK + key).getBytes()); |
| | | if(bytes == null || bytes.length == 0 ) { |
| | | return null; |
| | | } |
| | | return (T) Serialize.unSerialize(bytes); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long delete(String flag, String key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.del((flag + LINK + key).getBytes())); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.del((flag + LINK + key).getBytes()); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long clear(String flag) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | Jedis jedis = this.getJedis(); |
| | | this.setValue(flag, "CLEARING", "true"); |
| | | return withJedis(jedis -> { |
| | | try{ |
| | | Object returnValue = jedis.eval("local keys = redis.call('keys', ARGV[1]) for i=1,#keys,1000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end return #keys",0,flag + LINK + "*"); |
| | | return Long.parseLong(String.valueOf(returnValue)); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // 为已存在的key设置过期时间 - 秒 |
| | |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expire((flag + LINK + key).getBytes(), seconds); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | // 为已存在的key设置过期时间 - 具体到时间戳 (秒) |
| | |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expireAt((flag + LINK + key).getBytes(), toTime.getTime()/1000); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | // 获取过期剩余时间(秒) ttl == -1 没有设置过期时间; ttl == -2 key不存在 |
| | |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.ttl((flag + LINK + key).getBytes())); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.ttl((flag + LINK + key).getBytes()); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.set(flag + LINK + key, value)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.set(flag + LINK + key, value); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public String setValue(String flag, String key, String value, Integer seconds) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.setex(flag + LINK + key, seconds , value)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.setex(flag + LINK + key, seconds , value); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public String getValue(String flag, String key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.get(flag + LINK + key)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.get(flag + LINK + key); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long deleteValue(String flag, String... key) { |
| | |
| | | return null; |
| | | } |
| | | |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | String[] keys = new String[key.length]; |
| | | for(int i=0;i<key.length;i++){ |
| | | keys[i] = flag + LINK + key[i]; |
| | | } |
| | | return jedis.del(keys); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long clearValue(String flag) { |
| | |
| | | } |
| | | |
| | | this.setValue(flag, "CLEARING", "true"); |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | |
| | | try{ |
| | | Object returnValue = jedis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))",0,flag + LINK + "*"); |
| | | return Long.parseLong(String.valueOf(returnValue)); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public void setValueExpire(String flag, String key,int seconds){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expire((flag + LINK + key).getBytes(), seconds); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | public void setValueExpireAt(String flag, String key,Date atTime){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expireAt((flag + LINK + key).getBytes(), atTime.getTime()/1000); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | deleteMap(name,key); |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.hset(name.getBytes(), key.getBytes(), Serialize.serialize(value))); |
| | | Jedis jedis = this.getJedis(); |
| | | try { |
| | | return jedis.hset(name.getBytes(), key.getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public <T> T getMap(String name, String key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | byte[] bytes = jedis.hget(name.getBytes(), key.getBytes()); |
| | | if (bytes == null || bytes.length == 0) { |
| | | return null; |
| | | } |
| | | return (T) Serialize.unSerialize(bytes); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Set<String> getMapKeys(String name) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.hkeys(name)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.hkeys(name); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long deleteMap(String name, String... key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | String[] keys = new String[key.length]; |
| | | System.arraycopy(key, 0, keys, 0, key.length); |
| | | return jedis.hdel(name, keys); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long clearMap(String name) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.del(name)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.del(name); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public void setMapExpire(String name,int seconds){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expire(name.getBytes(), seconds); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | public void setMapExpireAt(String name,Date atTime){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expireAt(name.getBytes(), atTime.getTime()/1000); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | if(value == null){ |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.rpush(name.getBytes(), Serialize.serialize(value))); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.rpush(name.getBytes(), Serialize.serialize(value)); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // 获取列表头部元素 && 删除 |
| | |
| | | if(!this.initialize){ |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | byte[] bytes = jedis.lpop(name.getBytes()); |
| | | if(bytes == null || bytes.length == 0) { |
| | | return null; |
| | | } |
| | | return (T) Serialize.unSerialize(bytes); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | // 删除 |
| | |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.del(name)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.del(name); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public void setListExpire(String name, int seconds){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expire(name.getBytes(), seconds); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | public void setListExpireAt(String name, Date atTime){ |
| | | if(!this.initialize) { |
| | | return; |
| | | } |
| | | withJedisVoid(jedis -> { |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | jedis.expireAt(name.getBytes(), atTime.getTime()/1000); |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | } |
| | | |
| | | // count ---------------------------------------------------------------------------------------------------------- |
| | |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.incr("COUNT." + key)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.incr("COUNT." + key); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | public Long decr(String key) { |
| | | if(!this.initialize) { |
| | | return null; |
| | | } |
| | | return withJedis(jedis -> jedis.decr("COUNT." + key)); |
| | | Jedis jedis = this.getJedis(); |
| | | try{ |
| | | return jedis.decr("COUNT." + key); |
| | | } catch (Exception e) { |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
| | |
| | | @Slf4j |
| | | public class ExcelUtil { |
| | | private static final Pattern EXTEND_FIELD_SOURCE_PATTERN = Pattern.compile("^extendFields\\.\\[(.+)]$"); |
| | | private static final String SEQUENCE_SOURCE = "sequence"; |
| | | private static final String SEQUENCE_LABEL = "序号"; |
| | | |
| | | public static void build(Workbook workbook, HttpServletResponse response) { |
| | | response.reset(); |
| | |
| | | public static Workbook create(List<Map<String, Object>> rows, List<ExportColumn> columns, ExportMeta exportMeta) { |
| | | XSSFWorkbook workbook = new XSSFWorkbook(); |
| | | Sheet sheet = workbook.createSheet("export"); |
| | | int titleColumnCount = Math.max(columns.size(), 4); |
| | | List<ExportColumn> effectiveColumns = buildEffectiveColumns(columns, exportMeta); |
| | | boolean generatedSequenceColumn = hasGeneratedSequenceColumn(columns, effectiveColumns); |
| | | int titleColumnCount = Math.max(effectiveColumns.size(), 4); |
| | | int currentRowIndex = 0; |
| | | |
| | | CellStyle titleStyle = createTitleStyle(workbook); |
| | | CellStyle subHeaderStyle = createSubHeaderStyle(workbook); |
| | | CellStyle headerStyle = createHeaderStyle(workbook); |
| | | CellStyle bodyStyle = createBodyStyle(workbook); |
| | | |
| | | if (exportMeta != null && exportMeta.isLandscape()) { |
| | | sheet.getPrintSetup().setLandscape(true); |
| | | } |
| | | |
| | | if (exportMeta != null && StringUtils.isNotBlank(exportMeta.getReportTitle())) { |
| | | Row titleRow = sheet.createRow(currentRowIndex++); |
| | |
| | | } |
| | | |
| | | Row header = sheet.createRow(currentRowIndex++); |
| | | for (int index = 0; index < columns.size(); index++) { |
| | | for (int index = 0; index < effectiveColumns.size(); index++) { |
| | | Cell headerCell = header.createCell(index); |
| | | headerCell.setCellValue(columns.get(index).getLabel()); |
| | | headerCell.setCellValue(effectiveColumns.get(index).getLabel()); |
| | | headerCell.setCellStyle(headerStyle); |
| | | } |
| | | |
| | | int rowIndex = currentRowIndex; |
| | | for (Map<String, Object> rowData : rows) { |
| | | for (int dataIndex = 0; dataIndex < rows.size(); dataIndex++) { |
| | | Map<String, Object> rowData = rows.get(dataIndex); |
| | | Row row = sheet.createRow(rowIndex++); |
| | | for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) { |
| | | Object value = getRowValue(rowData, columns.get(columnIndex).getSource()); |
| | | for (int columnIndex = 0; columnIndex < effectiveColumns.size(); columnIndex++) { |
| | | Object value = getExportCellValue(rowData, effectiveColumns.get(columnIndex).getSource(), generatedSequenceColumn, dataIndex); |
| | | writeCellValue(row, columnIndex, value, bodyStyle); |
| | | } |
| | | } |
| | | |
| | | for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) { |
| | | for (int columnIndex = 0; columnIndex < effectiveColumns.size(); columnIndex++) { |
| | | sheet.autoSizeColumn(columnIndex); |
| | | } |
| | | |
| | | return workbook; |
| | | } |
| | | |
| | | private static List<ExportColumn> buildEffectiveColumns(List<ExportColumn> columns, ExportMeta exportMeta) { |
| | | List<ExportColumn> effectiveColumns = new ArrayList<>(columns); |
| | | if (exportMeta != null && exportMeta.isShowSequence() && !containsSequenceColumn(columns)) { |
| | | effectiveColumns.add(0, new ExportColumn(SEQUENCE_SOURCE, SEQUENCE_LABEL)); |
| | | } |
| | | return effectiveColumns; |
| | | } |
| | | |
| | | private static boolean containsSequenceColumn(List<ExportColumn> columns) { |
| | | for (ExportColumn column : columns) { |
| | | if (SEQUENCE_SOURCE.equals(column.getSource())) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private static boolean hasGeneratedSequenceColumn(List<ExportColumn> originalColumns, List<ExportColumn> effectiveColumns) { |
| | | return effectiveColumns.size() > originalColumns.size() |
| | | && !effectiveColumns.isEmpty() |
| | | && SEQUENCE_SOURCE.equals(effectiveColumns.get(0).getSource()); |
| | | } |
| | | |
| | | private static Object getExportCellValue( |
| | | Map<String, Object> rowData, |
| | | String source, |
| | | boolean generatedSequenceColumn, |
| | | int dataIndex |
| | | ) { |
| | | if (generatedSequenceColumn && SEQUENCE_SOURCE.equals(source)) { |
| | | return dataIndex + 1; |
| | | } |
| | | return getRowValue(rowData, source); |
| | | } |
| | | |
| | | private static Object getRowValue(Map<String, Object> rowData, String source) { |
| | |
| | | private final String printedAt; |
| | | private final String operator; |
| | | private final int count; |
| | | private final Map<String, Object> reportStyle; |
| | | |
| | | public ExportMeta(String reportTitle, String reportDate, String printedAt, String operator, int count) { |
| | | this(reportTitle, reportDate, printedAt, operator, count, Collections.emptyMap()); |
| | | } |
| | | |
| | | public ExportMeta( |
| | | String reportTitle, |
| | | String reportDate, |
| | | String printedAt, |
| | | String operator, |
| | | int count, |
| | | Map<String, ?> reportStyle |
| | | ) { |
| | | this.reportTitle = reportTitle; |
| | | this.reportDate = reportDate; |
| | | this.printedAt = printedAt; |
| | | this.operator = operator; |
| | | this.count = count; |
| | | this.reportStyle = normalizeReportStyle(reportStyle); |
| | | } |
| | | |
| | | public String getReportTitle() { |
| | |
| | | public int getCount() { |
| | | return count; |
| | | } |
| | | |
| | | public Map<String, Object> getReportStyle() { |
| | | return reportStyle; |
| | | } |
| | | |
| | | public boolean isLandscape() { |
| | | return "landscape".equalsIgnoreCase(Objects.toString(getReportStyleValue("orientation"), "")); |
| | | } |
| | | |
| | | public boolean isShowSequence() { |
| | | if (reportStyle.isEmpty() || !reportStyle.containsKey("showSequence")) { |
| | | return true; |
| | | } |
| | | Object value = getReportStyleValue("showSequence"); |
| | | if (value instanceof Boolean boolValue) { |
| | | return boolValue; |
| | | } |
| | | return !"false".equalsIgnoreCase(Objects.toString(value, "")); |
| | | } |
| | | |
| | | private Object getReportStyleValue(String key) { |
| | | return reportStyle.get(key); |
| | | } |
| | | |
| | | private static Map<String, Object> normalizeReportStyle(Map<String, ?> reportStyle) { |
| | | if (reportStyle == null || reportStyle.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | Map<String, Object> normalizedReportStyle = new HashMap<>(); |
| | | for (Map.Entry<String, ?> entry : reportStyle.entrySet()) { |
| | | normalizedReportStyle.put(entry.getKey(), entry.getValue()); |
| | | } |
| | | return Collections.unmodifiableMap(normalizedReportStyle); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.domain.KeyValVo; |
| | | import com.vincent.rsf.server.common.domain.PageParam; |
| | | import com.vincent.rsf.server.common.service.ListExportHandler; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.common.utils.ExcelUtil; |
| | | import com.vincent.rsf.server.system.controller.param.RoleScopeParam; |
| | | import com.vincent.rsf.server.system.entity.Role; |
| | |
| | | private RoleService roleService; |
| | | @Autowired |
| | | private RoleMenuService roleMenuService; |
| | | @Autowired |
| | | private ListExportService listExportService; |
| | | |
| | | private final ListExportHandler<Role, BaseParam> roleExportHandler = new ListExportHandler<>() { |
| | | @Override |
| | | public List<Role> listByIds(List<Long> ids) { |
| | | return roleService.listByIds(ids); |
| | | } |
| | | |
| | | @Override |
| | | public List<Role> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) { |
| | | PageParam<Role, BaseParam> pageParam = new PageParam<>(baseParam, Role.class); |
| | | return roleService.list(pageParam.buildWrapper(true)); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> toExportRow(Role record, List<ExcelUtil.ExportColumn> columns) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("name", record.getName()); |
| | | row.put("code", record.getCode()); |
| | | row.put("statusText", Objects.equals(record.getStatus(), 1) ? "正常" : "禁用"); |
| | | row.put("memo", record.getMemo()); |
| | | row.put("createTimeText", record.getCreateTime$()); |
| | | row.put("updateTimeText", record.getUpdateTime$()); |
| | | return row; |
| | | } |
| | | |
| | | @Override |
| | | public String defaultReportTitle() { |
| | | return "角色管理报表"; |
| | | } |
| | | }; |
| | | |
| | | @PreAuthorize("hasAuthority('system:role:list')") |
| | | @PostMapping("/role/page") |
| | |
| | | @PreAuthorize("hasAuthority('system:role:list')") |
| | | @PostMapping("/role/export") |
| | | public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception { |
| | | ExcelUtil.build(ExcelUtil.create(roleService.list(), Role.class), response); |
| | | listExportService.export(map, exportMap -> buildParam(exportMap, BaseParam.class), roleExportHandler, response); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:role:list')") |
| New file |
| | |
| | | package com.vincent.rsf.server.common.utils; |
| | | |
| | | import org.apache.poi.ss.usermodel.CellStyle; |
| | | import org.apache.poi.ss.usermodel.Font; |
| | | import org.apache.poi.ss.usermodel.HorizontalAlignment; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.lang.reflect.Constructor; |
| | | import java.lang.reflect.Modifier; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.junit.jupiter.api.Assertions.fail; |
| | | |
| | | class ExcelUtilReportStyleTest { |
| | | |
| | | @Test |
| | | void exportMetaExposesPublicSixArgReportStyleConstructorContract() { |
| | | Constructor<?> constructor = findPublicReportStyleConstructor( |
| | | "ExcelUtil.ExportMeta has not exposed a public reportStyle constructor contract yet; expected a public 6-arg constructor whose sixth parameter is a Map-compatible reportStyle payload." |
| | | ); |
| | | |
| | | assertEquals(6, constructor.getParameterCount(), "ExcelUtil.ExportMeta must expose a public 6-arg constructor for reportStyle."); |
| | | assertTrue( |
| | | Map.class.isAssignableFrom(constructor.getParameterTypes()[5]), |
| | | "ExcelUtil.ExportMeta public reportStyle constructor must accept a Map-compatible sixth argument." |
| | | ); |
| | | } |
| | | |
| | | @Test |
| | | void createBuildsSharedReportSkeletonWhenLandscapeReportStyleIsProvided() throws Exception { |
| | | Constructor<?> constructor = findPublicReportStyleConstructor( |
| | | "Cannot verify landscape reportStyle runtime behavior until ExcelUtil.ExportMeta exposes a public 6-arg constructor whose sixth parameter is a Map-compatible reportStyle payload." |
| | | ); |
| | | List<Map<String, Object>> rows = List.of(row("1", "管理员")); |
| | | List<ExcelUtil.ExportColumn> columns = List.of( |
| | | new ExcelUtil.ExportColumn("sequence", "序号"), |
| | | new ExcelUtil.ExportColumn("name", "角色名称") |
| | | ); |
| | | ExcelUtil.ExportMeta exportMeta = (ExcelUtil.ExportMeta) constructor.newInstance( |
| | | "角色管理报表", |
| | | "2026-03-29", |
| | | "2026-03-29 10:00:00", |
| | | "tester", |
| | | rows.size(), |
| | | Map.of( |
| | | "orientation", "landscape", |
| | | "showSequence", true |
| | | ) |
| | | ); |
| | | |
| | | try (Workbook workbook = ExcelUtil.create(rows, columns, exportMeta)) { |
| | | Sheet sheet = workbook.getSheetAt(0); |
| | | |
| | | assertEquals("角色管理报表", sheet.getRow(0).getCell(0).getStringCellValue()); |
| | | |
| | | CellStyle titleStyle = sheet.getRow(0).getCell(0).getCellStyle(); |
| | | assertEquals(HorizontalAlignment.CENTER, titleStyle.getAlignment()); |
| | | |
| | | Font titleFont = workbook.getFontAt(titleStyle.getFontIndex()); |
| | | assertTrue(titleFont.getFontHeightInPoints() >= 14); |
| | | |
| | | assertTrue(sheet.getRow(1).getCell(0).getStringCellValue().startsWith("报表日期:")); |
| | | assertTrue(sheet.getPrintSetup().getLandscape()); |
| | | assertEquals("角色名称", sheet.getRow(3).getCell(1).getStringCellValue()); |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | void createPrependsSequenceColumnByDefaultWhenReportStyleOmitsShowSequence() throws Exception { |
| | | Constructor<?> constructor = findPublicReportStyleConstructor( |
| | | "Cannot verify default showSequence behavior until ExcelUtil.ExportMeta exposes a public 6-arg constructor whose sixth parameter is a Map-compatible reportStyle payload." |
| | | ); |
| | | List<Map<String, Object>> rows = List.of(row(null, "管理员")); |
| | | List<ExcelUtil.ExportColumn> columns = List.of( |
| | | new ExcelUtil.ExportColumn("name", "角色名称") |
| | | ); |
| | | ExcelUtil.ExportMeta exportMeta = (ExcelUtil.ExportMeta) constructor.newInstance( |
| | | "角色管理报表", |
| | | "2026-03-29", |
| | | "2026-03-29 10:00:00", |
| | | "tester", |
| | | rows.size(), |
| | | Map.of("orientation", "portrait") |
| | | ); |
| | | |
| | | try (Workbook workbook = ExcelUtil.create(rows, columns, exportMeta)) { |
| | | Sheet sheet = workbook.getSheetAt(0); |
| | | |
| | | assertEquals("序号", sheet.getRow(3).getCell(0).getStringCellValue()); |
| | | assertEquals("1", sheet.getRow(4).getCell(0).getStringCellValue()); |
| | | assertEquals("角色名称", sheet.getRow(3).getCell(1).getStringCellValue()); |
| | | } |
| | | } |
| | | |
| | | private Constructor<?> findPublicReportStyleConstructor(String missingConstructorMessage) { |
| | | for (Constructor<?> constructor : ExcelUtil.ExportMeta.class.getConstructors()) { |
| | | if (!Modifier.isPublic(constructor.getModifiers()) || constructor.getParameterCount() != 6) { |
| | | continue; |
| | | } |
| | | Class<?>[] parameterTypes = constructor.getParameterTypes(); |
| | | if (Map.class.isAssignableFrom(parameterTypes[5])) { |
| | | return constructor; |
| | | } |
| | | } |
| | | fail(missingConstructorMessage); |
| | | return null; |
| | | } |
| | | |
| | | private Map<String, Object> row(String sequence, String name) { |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("sequence", sequence); |
| | | row.put("name", name); |
| | | return row; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.system.controller; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.vincent.rsf.framework.common.R; |
| | | import com.vincent.rsf.server.common.domain.BaseParam; |
| | | import com.vincent.rsf.server.common.service.ListExportService; |
| | | import com.vincent.rsf.server.system.entity.Role; |
| | | import com.vincent.rsf.server.system.service.RoleMenuService; |
| | | import com.vincent.rsf.server.system.service.RoleService; |
| | | import jakarta.servlet.ServletOutputStream; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.usermodel.WorkbookFactory; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.junit.jupiter.api.extension.ExtendWith; |
| | | import org.mockito.Mock; |
| | | import org.mockito.junit.jupiter.MockitoExtension; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertAll; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.never; |
| | | import static org.mockito.Mockito.lenient; |
| | | import static org.mockito.Mockito.verify; |
| | | import static org.mockito.Mockito.when; |
| | | import static org.mockito.Mockito.doReturn; |
| | | |
| | | @ExtendWith(MockitoExtension.class) |
| | | class RoleControllerTest { |
| | | |
| | | @Mock |
| | | private RoleService roleService; |
| | | @Mock |
| | | private RoleMenuService roleMenuService; |
| | | @Mock |
| | | private HttpServletResponse response; |
| | | |
| | | private RoleController roleController; |
| | | private ListExportService listExportService; |
| | | private ByteArrayOutputStream exportBuffer; |
| | | |
| | | @BeforeEach |
| | | void setUp() throws Exception { |
| | | roleController = new TestableRoleController(); |
| | | ReflectionTestUtils.setField(roleController, "roleService", roleService); |
| | | ReflectionTestUtils.setField(roleController, "roleMenuService", roleMenuService); |
| | | listExportService = new ListExportService(); |
| | | ReflectionTestUtils.setField(roleController, "listExportService", listExportService); |
| | | exportBuffer = new ByteArrayOutputStream(); |
| | | lenient().doReturn(new ServletOutputStream() { |
| | | @Override |
| | | public void write(int b) { |
| | | exportBuffer.write(b); |
| | | } |
| | | |
| | | @Override |
| | | public boolean isReady() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public void setWriteListener(jakarta.servlet.WriteListener writeListener) { |
| | | } |
| | | }).when(response).getOutputStream(); |
| | | } |
| | | |
| | | @Test |
| | | void exportPrefersSelectedIdsEvenWhenFiltersArePresent() throws Exception { |
| | | |
| | | Role role = role(1L, "管理员", "ADMIN", 1, "系统角色"); |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("ids", List.of(1L, 2L)); |
| | | body.put("name", "管理员"); |
| | | body.put("current", 1); |
| | | body.put("pageSize", 20); |
| | | body.put("meta", Map.of( |
| | | "reportTitle", "角色管理报表", |
| | | "columns", List.of( |
| | | Map.of("source", "name", "label", "角色名称"), |
| | | Map.of("source", "statusText", "label", "状态"), |
| | | Map.of("source", "memo", "label", "备注") |
| | | ) |
| | | )); |
| | | |
| | | when(roleService.listByIds(List.of(1L, 2L))).thenReturn(List.of(role)); |
| | | |
| | | roleController.export(body, response); |
| | | |
| | | verify(roleService).listByIds(List.of(1L, 2L)); |
| | | verify(roleService, never()).list(anyWrapper()); |
| | | assertExportSheet( |
| | | exportBuffer.toByteArray(), |
| | | List.of("序号", "角色名称", "状态", "备注"), |
| | | List.of("1", "管理员", "正常", "系统角色") |
| | | ); |
| | | } |
| | | |
| | | @Test |
| | | void pageStillAcceptsFlatPrintQueryWithoutFilterFields() { |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("current", 1); |
| | | body.put("pageSize", 200); |
| | | |
| | | when(roleService.page(any(), any())).thenReturn(new Page<>()); |
| | | |
| | | R result = roleController.page(body); |
| | | |
| | | assertNotNull(result); |
| | | verify(roleService).page(any(), any()); |
| | | } |
| | | |
| | | @Test |
| | | void exportUsesFilteredRoleListWhenIdsMissing() throws Exception { |
| | | |
| | | Role role = role(1L, "管理员", "ADMIN", 1, "系统角色"); |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("name", "管理员"); |
| | | body.put("current", 1); |
| | | body.put("pageSize", 200); |
| | | body.put("meta", Map.of( |
| | | "reportTitle", "角色管理报表", |
| | | "columns", List.of( |
| | | Map.of("source", "name", "label", "角色名称"), |
| | | Map.of("source", "statusText", "label", "状态") |
| | | ) |
| | | )); |
| | | |
| | | when(roleService.list(anyWrapper())).thenReturn(List.of(role)); |
| | | |
| | | roleController.export(body, response); |
| | | |
| | | verify(roleService).list(anyWrapper()); |
| | | verify(roleService, never()).listByIds(any()); |
| | | } |
| | | |
| | | @Test |
| | | void exportInsertsSequenceHeaderFromReportStyleWhenSequenceColumnIsNotRequested() throws Exception { |
| | | Role role = role(1L, "管理员", "ADMIN", 1, "系统角色"); |
| | | List<Map<String, Object>> requestedColumns = List.of( |
| | | Map.of("source", "name", "label", "角色名称"), |
| | | Map.of("source", "statusText", "label", "状态") |
| | | ); |
| | | Map<String, Object> body = new HashMap<>(); |
| | | body.put("ids", List.of(1L)); |
| | | body.put("meta", Map.of( |
| | | "reportTitle", "角色管理报表", |
| | | "reportDate", "2026-03-29", |
| | | "columns", requestedColumns, |
| | | "reportStyle", Map.of( |
| | | "showSequence", true |
| | | ) |
| | | )); |
| | | |
| | | when(roleService.listByIds(List.of(1L))).thenReturn(List.of(role)); |
| | | |
| | | roleController.export(body, response); |
| | | |
| | | try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(exportBuffer.toByteArray()))) { |
| | | Sheet sheet = workbook.getSheetAt(0); |
| | | assertEquals("角色管理报表", sheet.getRow(0).getCell(0).getStringCellValue()); |
| | | assertTrue(sheet.getRow(1).getCell(0).getStringCellValue().startsWith("报表日期:")); |
| | | assertAll("showSequence generated column", |
| | | () -> assertEquals("序号", sheet.getRow(3).getCell(0).getStringCellValue(), |
| | | "showSequence=true should prepend a generated 序号 column even when request columns omit it."), |
| | | () -> assertEquals("1", sheet.getRow(4).getCell(0).getStringCellValue(), |
| | | "showSequence=true should generate the first sequence cell as 1 in the exported data row.") |
| | | ); |
| | | assertEquals("角色名称", sheet.getRow(3).getCell(1).getStringCellValue(), |
| | | "Generated 序号 column should shift requested columns to the right."); |
| | | } |
| | | } |
| | | |
| | | private void assertExportSheet(byte[] workbookBytes, List<String> expectedHeaders, List<String> expectedFirstRow) throws Exception { |
| | | try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(workbookBytes))) { |
| | | Sheet sheet = workbook.getSheetAt(0); |
| | | for (int index = 0; index < expectedHeaders.size(); index++) { |
| | | assertEquals(expectedHeaders.get(index), sheet.getRow(3).getCell(index).getStringCellValue()); |
| | | assertEquals(expectedFirstRow.get(index), sheet.getRow(4).getCell(index).getStringCellValue()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Role role(Long id, String name, String code, Integer status, String memo) { |
| | | Role role = new Role(); |
| | | role.setId(id); |
| | | role.setName(name); |
| | | role.setCode(code); |
| | | role.setStatus(status); |
| | | role.setMemo(memo); |
| | | return role; |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private Wrapper<Role> anyWrapper() { |
| | | return any(Wrapper.class); |
| | | } |
| | | |
| | | private static final class TestableRoleController extends RoleController { |
| | | @Override |
| | | public <T extends BaseParam> T buildParam(Map<String, Object> map, Class<T> clz) { |
| | | try { |
| | | T param = clz.getDeclaredConstructor().newInstance(); |
| | | Map<String, Object> copy = new HashMap<>(map); |
| | | Object metaObject = copy.remove("meta"); |
| | | if (metaObject instanceof Map<?, ?> metaMap) { |
| | | metaMap.forEach((key, value) -> copy.put(String.valueOf(key), value)); |
| | | } |
| | | BaseParam baseParam = param; |
| | | baseParam.setCurrent(asInteger(copy.remove("current"))); |
| | | baseParam.setPageSize(asInteger(copy.remove("pageSize"))); |
| | | baseParam.setOrderBy(asString(copy.remove("orderBy"))); |
| | | baseParam.setCondition(asString(copy.remove("condition"))); |
| | | baseParam.setMap(copy); |
| | | return param; |
| | | } catch (Exception error) { |
| | | throw new RuntimeException(error); |
| | | } |
| | | } |
| | | |
| | | private Integer asInteger(Object value) { |
| | | return value == null ? null : Integer.valueOf(String.valueOf(value)); |
| | | } |
| | | |
| | | private String asString(Object value) { |
| | | return value == null ? null : String.valueOf(value); |
| | | } |
| | | } |
| | | } |