Преглед на файлове

Merge remote-tracking branch 'origin/master'

wangxx преди 1 седмица
родител
ревизия
04053ccaad

+ 47 - 6
airport-admin/src/main/java/com/sundot/airport/web/controller/score/ScoreEventController.java

@@ -11,6 +11,7 @@ import com.sundot.airport.common.core.page.TableDataInfo;
11 11
 import com.sundot.airport.common.enums.BusinessType;
12 12
 import com.sundot.airport.common.enums.ScoreTypeEnum;
13 13
 import com.sundot.airport.common.utils.DateUtils;
14
+import com.sundot.airport.common.utils.NameUtils;
14 15
 import com.sundot.airport.common.utils.poi.ExcelUtil;
15 16
 import com.sundot.airport.ledger.domain.ScoreEvent;
16 17
 import com.sundot.airport.ledger.domain.ScoreIndicator;
@@ -28,7 +29,12 @@ import org.springframework.web.multipart.MultipartFile;
28 29
 import javax.servlet.http.HttpServletResponse;
29 30
 import java.math.BigDecimal;
30 31
 import java.util.ArrayList;
32
+import java.util.Collections;
33
+import java.util.HashSet;
34
+import java.util.LinkedHashMap;
31 35
 import java.util.List;
36
+import java.util.Map;
37
+import java.util.Set;
32 38
 import java.util.UUID;
33 39
 
34 40
 /**
@@ -38,6 +44,16 @@ import java.util.UUID;
38 44
 @RequestMapping("/score/event")
39 45
 public class ScoreEventController extends BaseController {
40 46
 
47
+    // 排序字段白名单(防 SQL 注入)
48
+    private static final Set<String> ALLOWED_SORT_COLUMNS;
49
+
50
+    static {
51
+        Set<String> set = new HashSet<>();
52
+        set.add("create_time");
53
+        set.add("event_time");
54
+        ALLOWED_SORT_COLUMNS = Collections.unmodifiableSet(set);
55
+    }
56
+
41 57
     @Autowired
42 58
     private IScoreEventService service;
43 59
 
@@ -51,6 +67,9 @@ public class ScoreEventController extends BaseController {
51 67
     @GetMapping("/list")
52 68
     public TableDataInfo list(ScoreEvent query) {
53 69
         startPage();
70
+        // 排序字段白名单过滤
71
+        Map<String, String> safeSort = buildSafeSort(query.getSorts());
72
+        query.setSorts(safeSort);
54 73
         return getDataTable(service.selectList(query));
55 74
     }
56 75
 
@@ -75,10 +94,10 @@ public class ScoreEventController extends BaseController {
75 94
         entity.setSourceType("1");
76 95
         entity.setCreateBy(getUsername());
77 96
         entity.setCreateTime(DateUtils.getNowDate());
78
-        
97
+
79 98
         // 通过指标名称查找并设置ID
80 99
         resolveIndicatorIds(entity);
81
-        
100
+
82 101
         // 计算 totalScore
83 102
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
84 103
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
@@ -99,10 +118,10 @@ public class ScoreEventController extends BaseController {
99 118
     public AjaxResult edit(@Validated @RequestBody ScoreEvent entity) {
100 119
         entity.setUpdateBy(getUsername());
101 120
         entity.setUpdateTime(DateUtils.getNowDate());
102
-        
121
+
103 122
         // 通过指标名称查找并设置ID
104 123
         resolveIndicatorIds(entity);
105
-        
124
+
106 125
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
107 126
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
108 127
         entity.setTotalScore(score.add(cascade));
@@ -156,7 +175,7 @@ public class ScoreEventController extends BaseController {
156 175
                 entity.setLevel2Id(level2Id);
157 176
             }
158 177
         }
159
-        
178
+
160 179
         // 通过三级指标名称查找ID
161 180
         if (entity.getLevel3Name() != null && !entity.getLevel3Name().trim().isEmpty() && entity.getLevel3Id() == null) {
162 181
             Long level3Id = findIndicatorIdByName(entity.getLevel3Name().trim());
@@ -164,7 +183,7 @@ public class ScoreEventController extends BaseController {
164 183
                 entity.setLevel3Id(level3Id);
165 184
             }
166 185
         }
167
-        
186
+
168 187
         // 通过四级指标名称查找ID
169 188
         if (entity.getLevel4Name() != null && !entity.getLevel4Name().trim().isEmpty() && entity.getLevel4Id() == null) {
170 189
             Long level4Id = findIndicatorIdByName(entity.getLevel4Name().trim());
@@ -210,4 +229,26 @@ public class ScoreEventController extends BaseController {
210 229
         }
211 230
         return basePositionService.selectBasePositionById(regional.getParentId());
212 231
     }
232
+
233
+    /**
234
+     * 构建安全的排序参数
235
+     */
236
+    private Map<String, String> buildSafeSort(Map<String, String> sorts) {
237
+        Map<String, String> result = new LinkedHashMap<>();
238
+
239
+        if (sorts == null) {
240
+            return result;
241
+        }
242
+
243
+        for (Map.Entry<String, String> entry : sorts.entrySet()) {
244
+            String field = NameUtils.camelToUnderscore(entry.getKey());
245
+            if (!ALLOWED_SORT_COLUMNS.contains(field)) {
246
+                continue;
247
+            }
248
+            String direction = "desc".equalsIgnoreCase(String.valueOf(entry.getValue())) ? "desc" : "asc";
249
+
250
+            result.put(field, direction);
251
+        }
252
+        return result;
253
+    }
213 254
 }

+ 15 - 0
airport-common/src/main/java/com/sundot/airport/common/core/domain/BaseEntity.java

@@ -59,6 +59,13 @@ public class BaseEntity implements Serializable {
59 59
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
60 60
     private Map<String, Object> params;
61 61
 
62
+    /**
63
+     * 排序参数
64
+     */
65
+    @TableField(exist = false)
66
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
67
+    private Map<String, String> sorts;
68
+
62 69
     public String getSearchValue() {
63 70
         return searchValue;
64 71
     }
@@ -117,4 +124,12 @@ public class BaseEntity implements Serializable {
117 124
     public void setParams(Map<String, Object> params) {
118 125
         this.params = params;
119 126
     }
127
+
128
+    public Map<String, String> getSorts() {
129
+        return sorts;
130
+    }
131
+
132
+    public void setSorts(Map<String, String> sorts) {
133
+        this.sorts = sorts;
134
+    }
120 135
 }

+ 10 - 4
airport-common/src/main/java/com/sundot/airport/common/dto/LedgerCommonQueryReqVO.java

@@ -6,6 +6,7 @@ import org.springframework.format.annotation.DateTimeFormat;
6 6
 
7 7
 import java.io.Serializable;
8 8
 import java.util.Date;
9
+import java.util.Map;
9 10
 
10 11
 /**
11 12
  * 台账统计查询 Req Entity
@@ -36,15 +37,20 @@ public class LedgerCommonQueryReqVO implements Serializable {
36 37
     /**
37 38
      * 开始时间
38 39
      */
39
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
40
-    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
40
+    @JsonFormat(pattern = "yyyy-MM-dd")
41
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
41 42
     private Date startDate;
42 43
 
43 44
     /**
44 45
      * 结束时间
45 46
      */
46
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
47
-    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
47
+    @JsonFormat(pattern = "yyyy-MM-dd")
48
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
48 49
     private Date endDate;
49 50
 
51
+    /**
52
+     * 排序
53
+     */
54
+    Map<String, String> sorts;
55
+
50 56
 }

+ 28 - 0
airport-common/src/main/java/com/sundot/airport/common/utils/NameUtils.java

@@ -0,0 +1,28 @@
1
+package com.sundot.airport.common.utils;
2
+
3
+/**
4
+ * 字段名称工具类
5
+ */
6
+public class NameUtils {
7
+
8
+    /**
9
+     * 驼峰转下划线
10
+     * @param name
11
+     * @return
12
+     */
13
+    public static String camelToUnderscore(String name) {
14
+        if (name == null || name.isEmpty()) {
15
+            return name;
16
+        }
17
+        StringBuilder sb = new StringBuilder();
18
+        for (char c : name.toCharArray()) {
19
+            if (Character.isUpperCase(c)) {
20
+                sb.append("_").append(Character.toLowerCase(c));
21
+            } else {
22
+                sb.append(c);
23
+            }
24
+        }
25
+        return sb.toString();
26
+    }
27
+
28
+}

+ 62 - 0
airport-common/src/main/java/com/sundot/airport/common/utils/StreamSortUtils.java

@@ -0,0 +1,62 @@
1
+package com.sundot.airport.common.utils;
2
+
3
+import java.lang.reflect.Field;
4
+import java.util.Comparator;
5
+import java.util.List;
6
+import java.util.Map;
7
+import java.util.stream.Collectors;
8
+
9
+/**
10
+ * 排序工具类(支持 Integer /Long / BigDecimal / String / Date)
11
+ */
12
+public class StreamSortUtils {
13
+
14
+    /**
15
+     * 根据 Map 动态排序
16
+     *
17
+     * @param list   待排序集合
18
+     * @param sorts  key=字段名, value=asc/desc
19
+     */
20
+    public static <T> List<T> sort(List<T> list, Map<String, String> sorts) {
21
+        if (list == null || list.isEmpty() || sorts == null || sorts.isEmpty()) {
22
+            return list;
23
+        }
24
+
25
+        Comparator<T> comparator = null;
26
+
27
+        for (Map.Entry<String, String> entry : sorts.entrySet()) {
28
+            String fieldName = entry.getKey();
29
+            boolean desc = "desc".equalsIgnoreCase(entry.getValue());
30
+
31
+            Comparator<T> current = Comparator.comparing(
32
+                    t -> getFieldValue(t, fieldName),
33
+                    Comparator.nullsLast(Comparator.naturalOrder())
34
+            );
35
+
36
+            if (desc) {
37
+                current = current.reversed();
38
+            }
39
+
40
+            comparator = comparator == null ? current : comparator.thenComparing(current);
41
+        }
42
+
43
+        return list.stream()
44
+                .sorted(comparator)
45
+                .collect(Collectors.toList());
46
+    }
47
+
48
+    /**
49
+     * 反射获取字段值(支持 BigDecimal / String / Date)
50
+     */
51
+    @SuppressWarnings("unchecked")
52
+    private static <T> Comparable<Object> getFieldValue(T obj, String fieldName) {
53
+        try {
54
+            Field field = obj.getClass().getDeclaredField(fieldName);
55
+            field.setAccessible(true);
56
+            return (Comparable<Object>) field.get(obj);
57
+        } catch (Exception e) {
58
+            throw new RuntimeException("排序字段不存在或不可比较: " + fieldName, e);
59
+        }
60
+    }
61
+
62
+}

+ 10 - 1
airport-ledger/src/main/java/com/sundot/airport/ledger/service/impl/LedgerWarningServiceImpl.java

@@ -13,6 +13,7 @@ import com.sundot.airport.common.enums.LedgerStatusLabelEnum;
13 13
 import com.sundot.airport.common.enums.ScoreLevelEnum;
14 14
 import com.sundot.airport.common.utils.DeptUtils;
15 15
 import com.sundot.airport.common.utils.SecurityUtils;
16
+import com.sundot.airport.common.utils.StreamSortUtils;
16 17
 import com.sundot.airport.ledger.domain.ScoreEvent;
17 18
 import com.sundot.airport.ledger.domain.ScoreIndicator;
18 19
 import com.sundot.airport.ledger.domain.vo.LedgerWarningDetailItemVO;
@@ -33,6 +34,7 @@ import java.util.ArrayList;
33 34
 import java.util.Arrays;
34 35
 import java.util.HashMap;
35 36
 import java.util.HashSet;
37
+import java.util.LinkedHashMap;
36 38
 import java.util.List;
37 39
 import java.util.Map;
38 40
 import java.util.Set;
@@ -187,7 +189,14 @@ public class LedgerWarningServiceImpl implements ILedgerWarningService {
187 189
         BigDecimal sum = ledgerWarningDetailItemList.stream().map(LedgerWarningDetailItemVO::getOverallScore).reduce(BigDecimal.ZERO, BigDecimal::add);
188 190
         BigDecimal averageComprehensiveScore = CollUtil.isEmpty(ledgerWarningDetailItemList) ? BigDecimal.ZERO : sum.divide(BigDecimal.valueOf(ledgerWarningDetailItemList.size()), 2, RoundingMode.HALF_UP);
189 191
         result.setAverageComprehensiveScore(averageComprehensiveScore);
190
-        result.setLedgerWarningDetailItemList(ledgerWarningDetailItemList);
192
+        // 排序
193
+        if (ObjUtil.isNull(queryReq.getSorts())) {
194
+            Map<String, String> sorts = new LinkedHashMap<>();
195
+            sorts.put("overallScore", "desc");
196
+            queryReq.setSorts(sorts);
197
+        }
198
+        List<LedgerWarningDetailItemVO> ledgerWarningDetailItemListSort = StreamSortUtils.sort(ledgerWarningDetailItemList, queryReq.getSorts());
199
+        result.setLedgerWarningDetailItemList(ledgerWarningDetailItemListSort);
191 200
         return result;
192 201
     }
193 202
 

+ 18 - 1
airport-ledger/src/main/resources/mapper/ledger/ScoreEventMapper.xml

@@ -62,6 +62,23 @@
62 62
         WHERE del_flag = '0'
63 63
     </sql>
64 64
 
65
+    <sql id="order_by">
66
+        <choose>
67
+            <when test="sorts != null and sorts.size() > 0">
68
+                order by
69
+                <foreach collection="sorts.entrySet()"
70
+                         index="field"
71
+                         item="dir"
72
+                         separator=",">
73
+                    ${field} ${dir}
74
+                </foreach>
75
+            </when>
76
+            <otherwise>
77
+                order by event_time desc
78
+            </otherwise>
79
+        </choose>
80
+    </sql>
81
+
65 82
     <select id="selectList" parameterType="com.sundot.airport.ledger.domain.ScoreEvent"
66 83
             resultMap="ScoreEventResult">
67 84
         <include refid="selectVo"/>
@@ -96,7 +113,7 @@
96 113
         <if test="params != null and params.endTime != null and params.endTime != ''">
97 114
             AND event_time &lt;= #{params.endTime}
98 115
         </if>
99
-        ORDER BY event_time DESC, id DESC
116
+        <include refid="order_by"/>
100 117
     </select>
101 118
 
102 119
 </mapper>