Kaynağa Gözat

Merge remote-tracking branch 'origin/master'

wangxx 1 hafta önce
ebeveyn
işleme
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
 import com.sundot.airport.common.enums.BusinessType;
11
 import com.sundot.airport.common.enums.BusinessType;
12
 import com.sundot.airport.common.enums.ScoreTypeEnum;
12
 import com.sundot.airport.common.enums.ScoreTypeEnum;
13
 import com.sundot.airport.common.utils.DateUtils;
13
 import com.sundot.airport.common.utils.DateUtils;
14
+import com.sundot.airport.common.utils.NameUtils;
14
 import com.sundot.airport.common.utils.poi.ExcelUtil;
15
 import com.sundot.airport.common.utils.poi.ExcelUtil;
15
 import com.sundot.airport.ledger.domain.ScoreEvent;
16
 import com.sundot.airport.ledger.domain.ScoreEvent;
16
 import com.sundot.airport.ledger.domain.ScoreIndicator;
17
 import com.sundot.airport.ledger.domain.ScoreIndicator;
@@ -28,7 +29,12 @@ import org.springframework.web.multipart.MultipartFile;
28
 import javax.servlet.http.HttpServletResponse;
29
 import javax.servlet.http.HttpServletResponse;
29
 import java.math.BigDecimal;
30
 import java.math.BigDecimal;
30
 import java.util.ArrayList;
31
 import java.util.ArrayList;
32
+import java.util.Collections;
33
+import java.util.HashSet;
34
+import java.util.LinkedHashMap;
31
 import java.util.List;
35
 import java.util.List;
36
+import java.util.Map;
37
+import java.util.Set;
32
 import java.util.UUID;
38
 import java.util.UUID;
33
 
39
 
34
 /**
40
 /**
@@ -38,6 +44,16 @@ import java.util.UUID;
38
 @RequestMapping("/score/event")
44
 @RequestMapping("/score/event")
39
 public class ScoreEventController extends BaseController {
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
     @Autowired
57
     @Autowired
42
     private IScoreEventService service;
58
     private IScoreEventService service;
43
 
59
 
@@ -51,6 +67,9 @@ public class ScoreEventController extends BaseController {
51
     @GetMapping("/list")
67
     @GetMapping("/list")
52
     public TableDataInfo list(ScoreEvent query) {
68
     public TableDataInfo list(ScoreEvent query) {
53
         startPage();
69
         startPage();
70
+        // 排序字段白名单过滤
71
+        Map<String, String> safeSort = buildSafeSort(query.getSorts());
72
+        query.setSorts(safeSort);
54
         return getDataTable(service.selectList(query));
73
         return getDataTable(service.selectList(query));
55
     }
74
     }
56
 
75
 
@@ -75,10 +94,10 @@ public class ScoreEventController extends BaseController {
75
         entity.setSourceType("1");
94
         entity.setSourceType("1");
76
         entity.setCreateBy(getUsername());
95
         entity.setCreateBy(getUsername());
77
         entity.setCreateTime(DateUtils.getNowDate());
96
         entity.setCreateTime(DateUtils.getNowDate());
78
-        
97
+
79
         // 通过指标名称查找并设置ID
98
         // 通过指标名称查找并设置ID
80
         resolveIndicatorIds(entity);
99
         resolveIndicatorIds(entity);
81
-        
100
+
82
         // 计算 totalScore
101
         // 计算 totalScore
83
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
102
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
84
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
103
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
@@ -99,10 +118,10 @@ public class ScoreEventController extends BaseController {
99
     public AjaxResult edit(@Validated @RequestBody ScoreEvent entity) {
118
     public AjaxResult edit(@Validated @RequestBody ScoreEvent entity) {
100
         entity.setUpdateBy(getUsername());
119
         entity.setUpdateBy(getUsername());
101
         entity.setUpdateTime(DateUtils.getNowDate());
120
         entity.setUpdateTime(DateUtils.getNowDate());
102
-        
121
+
103
         // 通过指标名称查找并设置ID
122
         // 通过指标名称查找并设置ID
104
         resolveIndicatorIds(entity);
123
         resolveIndicatorIds(entity);
105
-        
124
+
106
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
125
         BigDecimal score = entity.getScoreValue() != null ? entity.getScoreValue() : BigDecimal.ZERO;
107
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
126
         BigDecimal cascade = entity.getCascadeScore() != null ? entity.getCascadeScore() : BigDecimal.ZERO;
108
         entity.setTotalScore(score.add(cascade));
127
         entity.setTotalScore(score.add(cascade));
@@ -156,7 +175,7 @@ public class ScoreEventController extends BaseController {
156
                 entity.setLevel2Id(level2Id);
175
                 entity.setLevel2Id(level2Id);
157
             }
176
             }
158
         }
177
         }
159
-        
178
+
160
         // 通过三级指标名称查找ID
179
         // 通过三级指标名称查找ID
161
         if (entity.getLevel3Name() != null && !entity.getLevel3Name().trim().isEmpty() && entity.getLevel3Id() == null) {
180
         if (entity.getLevel3Name() != null && !entity.getLevel3Name().trim().isEmpty() && entity.getLevel3Id() == null) {
162
             Long level3Id = findIndicatorIdByName(entity.getLevel3Name().trim());
181
             Long level3Id = findIndicatorIdByName(entity.getLevel3Name().trim());
@@ -164,7 +183,7 @@ public class ScoreEventController extends BaseController {
164
                 entity.setLevel3Id(level3Id);
183
                 entity.setLevel3Id(level3Id);
165
             }
184
             }
166
         }
185
         }
167
-        
186
+
168
         // 通过四级指标名称查找ID
187
         // 通过四级指标名称查找ID
169
         if (entity.getLevel4Name() != null && !entity.getLevel4Name().trim().isEmpty() && entity.getLevel4Id() == null) {
188
         if (entity.getLevel4Name() != null && !entity.getLevel4Name().trim().isEmpty() && entity.getLevel4Id() == null) {
170
             Long level4Id = findIndicatorIdByName(entity.getLevel4Name().trim());
189
             Long level4Id = findIndicatorIdByName(entity.getLevel4Name().trim());
@@ -210,4 +229,26 @@ public class ScoreEventController extends BaseController {
210
         }
229
         }
211
         return basePositionService.selectBasePositionById(regional.getParentId());
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
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
59
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
60
     private Map<String, Object> params;
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
     public String getSearchValue() {
69
     public String getSearchValue() {
63
         return searchValue;
70
         return searchValue;
64
     }
71
     }
@@ -117,4 +124,12 @@ public class BaseEntity implements Serializable {
117
     public void setParams(Map<String, Object> params) {
124
     public void setParams(Map<String, Object> params) {
118
         this.params = params;
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
 import java.io.Serializable;
7
 import java.io.Serializable;
8
 import java.util.Date;
8
 import java.util.Date;
9
+import java.util.Map;
9
 
10
 
10
 /**
11
 /**
11
  * 台账统计查询 Req Entity
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
     private Date startDate;
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
     private Date endDate;
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
 import com.sundot.airport.common.enums.ScoreLevelEnum;
13
 import com.sundot.airport.common.enums.ScoreLevelEnum;
14
 import com.sundot.airport.common.utils.DeptUtils;
14
 import com.sundot.airport.common.utils.DeptUtils;
15
 import com.sundot.airport.common.utils.SecurityUtils;
15
 import com.sundot.airport.common.utils.SecurityUtils;
16
+import com.sundot.airport.common.utils.StreamSortUtils;
16
 import com.sundot.airport.ledger.domain.ScoreEvent;
17
 import com.sundot.airport.ledger.domain.ScoreEvent;
17
 import com.sundot.airport.ledger.domain.ScoreIndicator;
18
 import com.sundot.airport.ledger.domain.ScoreIndicator;
18
 import com.sundot.airport.ledger.domain.vo.LedgerWarningDetailItemVO;
19
 import com.sundot.airport.ledger.domain.vo.LedgerWarningDetailItemVO;
@@ -33,6 +34,7 @@ import java.util.ArrayList;
33
 import java.util.Arrays;
34
 import java.util.Arrays;
34
 import java.util.HashMap;
35
 import java.util.HashMap;
35
 import java.util.HashSet;
36
 import java.util.HashSet;
37
+import java.util.LinkedHashMap;
36
 import java.util.List;
38
 import java.util.List;
37
 import java.util.Map;
39
 import java.util.Map;
38
 import java.util.Set;
40
 import java.util.Set;
@@ -187,7 +189,14 @@ public class LedgerWarningServiceImpl implements ILedgerWarningService {
187
         BigDecimal sum = ledgerWarningDetailItemList.stream().map(LedgerWarningDetailItemVO::getOverallScore).reduce(BigDecimal.ZERO, BigDecimal::add);
189
         BigDecimal sum = ledgerWarningDetailItemList.stream().map(LedgerWarningDetailItemVO::getOverallScore).reduce(BigDecimal.ZERO, BigDecimal::add);
188
         BigDecimal averageComprehensiveScore = CollUtil.isEmpty(ledgerWarningDetailItemList) ? BigDecimal.ZERO : sum.divide(BigDecimal.valueOf(ledgerWarningDetailItemList.size()), 2, RoundingMode.HALF_UP);
190
         BigDecimal averageComprehensiveScore = CollUtil.isEmpty(ledgerWarningDetailItemList) ? BigDecimal.ZERO : sum.divide(BigDecimal.valueOf(ledgerWarningDetailItemList.size()), 2, RoundingMode.HALF_UP);
189
         result.setAverageComprehensiveScore(averageComprehensiveScore);
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
         return result;
200
         return result;
192
     }
201
     }
193
 
202
 

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

@@ -62,6 +62,23 @@
62
         WHERE del_flag = '0'
62
         WHERE del_flag = '0'
63
     </sql>
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
     <select id="selectList" parameterType="com.sundot.airport.ledger.domain.ScoreEvent"
82
     <select id="selectList" parameterType="com.sundot.airport.ledger.domain.ScoreEvent"
66
             resultMap="ScoreEventResult">
83
             resultMap="ScoreEventResult">
67
         <include refid="selectVo"/>
84
         <include refid="selectVo"/>
@@ -96,7 +113,7 @@
96
         <if test="params != null and params.endTime != null and params.endTime != ''">
113
         <if test="params != null and params.endTime != null and params.endTime != ''">
97
             AND event_time &lt;= #{params.endTime}
114
             AND event_time &lt;= #{params.endTime}
98
         </if>
115
         </if>
99
-        ORDER BY event_time DESC, id DESC
116
+        <include refid="order_by"/>
100
     </select>
117
     </select>
101
 
118
 
102
 </mapper>
119
 </mapper>