From 3bb4fb15c664d29d179083698fdad35a661b1d7f Mon Sep 17 00:00:00 2001
From: riku <risaku@163.com>
Date: 星期四, 28 八月 2025 14:57:40 +0800
Subject: [PATCH] 2025.8.28 1. 添加走航季度报告相关统计功能(待完成)
---
src/main/kotlin/com/flightfeather/uav/biz/mission/MissionUtil.kt | 49 +++-
src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt | 2
src/main/resources/templates/underway_season_report.docx | 0
src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt | 4
src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt | 81 +++++++
pom.xml | 24 ++
src/main/kotlin/com/flightfeather/uav/common/utils/FileUtil.kt | 4
src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt | 28 ++
src/main/kotlin/com/flightfeather/uav/biz/report/UnderwaySeasonReport.kt | 72 +++++++
src/main/kotlin/com/flightfeather/uav/common/utils/CommonUtil.kt | 2
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImpl.kt | 2
src/main/kotlin/com/flightfeather/uav/common/file/Docx4jGenerator.kt | 114 +++++++++++
src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt | 115 +++++++++++
src/main/kotlin/com/flightfeather/uav/common/utils/ObjToMapUtil.kt | 62 ++++++
14 files changed, 533 insertions(+), 26 deletions(-)
diff --git a/pom.xml b/pom.xml
index 5655f62..6b67ad9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -267,6 +267,30 @@
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
+ <dependency>
+ <groupId>org.docx4j</groupId>
+ <artifactId>docx4j-core</artifactId>
+ <version>11.4.9</version> <!-- 浣跨敤鏈�鏂扮ǔ瀹氱増 -->
+ </dependency>
+ <!-- 濡傞渶澶勭悊鍥剧墖/琛ㄦ牸绛夐珮绾у姛鑳斤紝鍙坊鍔� -->
+ <dependency>
+ <groupId>org.docx4j</groupId>
+ <artifactId>docx4j-ImportXHTML</artifactId>
+ <version>11.4.8</version>
+ </dependency>
+ <!-- MockK 鍗曞厓娴嬭瘯搴擄紙鐢ㄤ簬 Kotlin锛� -->
+ <dependency>
+ <groupId>io.mockk</groupId>
+ <artifactId>mockk</artifactId>
+ <version>1.14.5</version> <!-- 浣跨敤鏈�鏂扮ǔ瀹氱増 -->
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter</artifactId>
+ <version>RELEASE</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/mission/MissionUtil.kt b/src/main/kotlin/com/flightfeather/uav/biz/mission/MissionUtil.kt
index b5017c7..1edaf99 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/mission/MissionUtil.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/mission/MissionUtil.kt
@@ -1,7 +1,9 @@
package com.flightfeather.uav.biz.mission
+import com.flightfeather.uav.common.net.AMapService
import com.flightfeather.uav.common.utils.MapUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
+import com.flightfeather.uav.domain.entity.avg
/**
* 璧拌埅浠诲姟璁$畻宸ュ叿
@@ -15,18 +17,43 @@
*/
fun calKilometres(data: List<BaseRealTimeData>): Double {
var distance = .0
- for (i in 1 until data.size) {
- val a = data[i - 1]
- val b = data[i]
- if (a.longitude == null || a.latitude == null || b.longitude == null || b.latitude == null) continue
+ var lastValidPoint: BaseRealTimeData? = null
- distance += MapUtil.getDistance(
- a.longitude!!.toDouble(),
- a.latitude!!.toDouble(),
- b.longitude!!.toDouble(),
- b.latitude!!.toDouble()
- )
+ for (point in data) {
+ // 璺宠繃鏃犳晥鐐�
+ if (point.longitude == null || point.latitude == null) continue
+
+ // 濡傛灉瀛樺湪涓婁竴涓湁鏁堢偣锛屽垯璁$畻璺濈
+ lastValidPoint?.let { prevPoint ->
+ distance += MapUtil.getDistance(
+ prevPoint.longitude!!.toDouble(),
+ prevPoint.latitude!!.toDouble(),
+ point.longitude!!.toDouble(),
+ point.latitude!!.toDouble()
+ )
+ }
+
+ // 鏇存柊涓婁竴涓湁鏁堢偣
+ lastValidPoint = point
}
return distance
}
-}
\ No newline at end of file
+
+ /**
+ * 鏍规嵁杞ㄨ抗鐐硅绠楁墍灞炲尯鍩燂紙涔¢晣+琛楅亾锛�
+ * @param data 璧拌埅杞ㄨ抗鐐瑰垪琛�
+ * @return 鍖哄煙鍚嶇О锛堜埂闀�+琛楅亾锛夛紝鑻ユ棤娉曡绠楀垯杩斿洖null
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun calRegion(data: List<BaseRealTimeData>): String? {
+ // 璁$畻鎵�鏈夎建杩圭偣鐨勫钩鍧囧潗鏍囷紙涓績鐐癸級
+ val avgData = data.avg()
+ val pair = avgData.longitude?.toDouble() to avgData.latitude?.toDouble()
+ // 鑻ュ钩鍧囧潗鏍囨棤鏁堝垯杩斿洖null
+ if (pair.first == null || pair.second == null) return null
+ // 灏哤GS84鍧愭爣杞崲涓篏CJ02鍧愭爣鍚庤繘琛岄�嗗湴鐞嗙紪鐮佽幏鍙栧湴鍧�淇℃伅
+ val address = AMapService.reGeo(MapUtil.wgs84ToGcj02(pair as Pair<Double, Double>))
+ // 杩斿洖涔¢晣鍜岃閬撳悕绉扮粍鍚�
+ return address.township + address.street
+ }
+}
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt
index 73855b5..f61020e 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionClue.kt
@@ -8,7 +8,7 @@
*/
class MissionClue {
- inner class Clue{
+ class Clue{
var factor:FactorType?=null
var riskRegion:String?=null
}
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt
index 701526e..fe910c4 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionInventory.kt
@@ -14,7 +14,7 @@
class MissionInventory {
// 璧拌埅娓呭崟淇℃伅
- inner class MissionInfo : Mission() {
+ class MissionInfo : Mission() {
// 棣栬姹℃煋鐗�
var mainFactor: String? = null
@@ -26,7 +26,7 @@
}
// 璧拌埅璇︽儏淇℃伅
- inner class MissionDetail : Mission() {
+ class MissionDetail : Mission() {
var keyScene: List<SceneInfo>? = null
var dataStatistics: List<FactorStatistics>? = null
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt
index beb76d5..3cc0cbb 100644
--- a/src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/MissionSummary.kt
@@ -1,16 +1,29 @@
package com.flightfeather.uav.biz.report
+import com.flightfeather.uav.biz.sourcetrace.model.PollutedClue
+import com.flightfeather.uav.domain.entity.Mission
+import com.flightfeather.uav.domain.repository.MissionRep
+import com.flightfeather.uav.lightshare.bean.AreaVo
+import com.flightfeather.uav.socket.eunm.FactorType
+import com.flightfeather.uav.socket.sender.MsgType
import org.springframework.stereotype.Component
+import java.util.*
+import kotlin.math.round
/**
* 璧拌埅浠诲姟姹囨��
* @date 2025/8/22
* @author feiyu02
*/
-@Component
-class MissionSummary {
+class MissionSummary() {
- inner class Summary(
+ data class Summary(
+ // 姹囨�诲懆鏈熷紑濮嬫椂闂�
+ val startTime: Date,
+ // 姹囨�诲懆鏈熺粨鏉熸椂闂�
+ val endTime: Date,
+ // 璧拌埅鍖哄煙淇℃伅
+ val area: AreaVo,
// 璧拌埅娆℃暟
val count: Int,
// 鎬婚噷绋嬫暟锛堝叕閲岋級
@@ -27,7 +40,101 @@
val probByFactor:List<Triple<String, Int, Double>>
)
- fun execute() {
+ /**
+ * 鏍规嵁鏃堕棿鑼冨洿鏌ヨ璧拌埅浠诲姟骞剁敓鎴愮粺璁$粨鏋�
+ * @param startTime 缁熻寮�濮嬫椂闂�
+ * @param endTime 缁熻缁撴潫鏃堕棿
+ * @param missions 璧拌埅浠诲姟鍒楄〃锛堝閮ㄤ紶鍏ワ紝閬垮厤閲嶅鏌ヨ锛�
+ * @param clues 姹℃煋绾跨储鍒楄〃锛堢敤浜庨棶棰樼粺璁★級
+ * @return 璧拌埅浠诲姟缁熻缁撴灉Summary
+ */
+ fun execute(startTime: Date, endTime: Date, missions: List<Mission?>, clues: List<PollutedClue?>): Summary {
+ // 1. 鏌ヨ鎸囧畾鏃堕棿鑼冨洿鍐呯殑璧拌埅浠诲姟
+ if (missions.isEmpty()) {
+ return Summary(
+ startTime = startTime,
+ endTime = endTime,
+ area = AreaVo(), // 绌轰换鍔℃椂杩斿洖榛樿鍖哄煙淇℃伅
+ count = 0,
+ kilometres = 0.0,
+ regionList = emptyList(),
+ countByDegree = emptyList(),
+ probCount = 0,
+ highRiskSceneCount = 0,
+ probByFactor = emptyList()
+ )
+ }
+ // 2. 鍩虹缁熻锛氭�讳换鍔℃暟銆佹�婚噷绋�
+ val totalCount = missions.size
+ val totalKilometres = missions.sumOf { it?.kilometres?.toDouble() ?: 0.0 }
+
+ // 3. 鍖哄煙淇℃伅锛氬彇棣栦釜浠诲姟鐨勮鏀垮尯鍒掍俊鎭紙濡傚瓨鍦ㄥ鍖哄煙鍙墿灞曚负鍚堝苟閫昏緫锛�
+ val firstMission = missions.first()
+ val area = AreaVo().apply {
+ provinceCode = firstMission?.provinceCode
+ provinceName = firstMission?.provinceName
+ cityCode = firstMission?.cityCode
+ cityName = firstMission?.cityName
+ districtCode = firstMission?.districtCode
+ districtName = firstMission?.districtName
+ townCode = firstMission?.townCode
+ townName = firstMission?.townName
+ }
+
+ // 4. 娑夊強鍖哄煙鍒楄〃锛氬幓閲嶆敹闆嗘墍鏈変换鍔$殑region瀛楁
+ val regionList = missions.mapNotNull { it?.region }.distinct()
+
+ // 5. 绌烘皵璐ㄩ噺绛夌骇鍒嗗竷锛氭寜pollutionDegree鍒嗙粍缁熻娆℃暟鍙婂崰姣�
+ val degreeGroups = missions
+ .filter { it?.pollutionDegree != null } // 杩囨护鏃犳晥绛夌骇
+ .groupBy { it?.pollutionDegree!! }
+ .mapValues { it.value.size }
+ val countByDegree = degreeGroups.map { (degree, count) ->
+ Triple(degree, count, count.toDouble() / totalCount)
+ }
+
+ // 6. 闂鐩稿叧缁熻锛堢ず渚嬶細姝ゅ鍋囪闇�鍏宠仈鍏朵粬琛紝鏆傝繑鍥�0锛屽疄闄呴渶鏍规嵁涓氬姟琛ュ厖锛�
+ val clueRes = calClue(clues)
+ val probCount = clueRes.first // 闇�鍏宠仈闂琛ㄧ粺璁�
+ val highRiskSceneCount = clueRes.second // 闇�鍏宠仈鍦烘櫙琛ㄧ粺璁�
+ val probByFactor = clueRes.third
+
+ // 7. 鏋勫缓骞惰繑鍥炵粺璁$粨鏋�
+ return Summary(
+ startTime = startTime,
+ endTime = endTime,
+ area = area,
+ count = totalCount,
+ kilometres = totalKilometres,
+ regionList = regionList,
+ countByDegree = countByDegree,
+ probCount = probCount,
+ highRiskSceneCount = highRiskSceneCount,
+ probByFactor = probByFactor
+ )
+ }
+
+ private fun calClue(clues: List<PollutedClue?>): Triple<Int, Int, List<Triple<String, Int, Double>>> {
+ var probCount = 0 // 闇�鍏宠仈闂琛ㄧ粺璁�
+ var highRiskSceneCount = 0 // 闇�鍏宠仈鍦烘櫙琛ㄧ粺璁�
+ val probByFactorMap = mutableMapOf<FactorType, Int>() // 闇�鍏宠仈鍥犲瓙琛ㄧ粺璁�
+ clues.forEach { c ->
+ if (c?.msgType == MsgType.PolClue.value) {
+ c.pollutedSource?.sceneList?.size?.let { s -> highRiskSceneCount += s }
+ c.pollutedData?.statisticMap?.keys?.forEach { k ->
+ probCount++
+ if (!probByFactorMap.containsKey(k)) {
+ probByFactorMap[k] = 0
+ }
+ probByFactorMap[k] = probByFactorMap[k]!! + 1
+ }
+ }
+ }
+ val probByFactor = probByFactorMap.entries.map {
+ val per = if(probCount == 0) .0 else round(it.value.toDouble() / probCount * 100) / 100
+ Triple(it.key.des, it.value, per)
+ }
+ return Triple(probCount, highRiskSceneCount, probByFactor)
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/biz/report/UnderwaySeasonReport.kt b/src/main/kotlin/com/flightfeather/uav/biz/report/UnderwaySeasonReport.kt
new file mode 100644
index 0000000..b6366d2
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/biz/report/UnderwaySeasonReport.kt
@@ -0,0 +1,72 @@
+//package com.flightfeather.uav.biz.report
+//
+//import com.flightfeather.uav.common.file.Docx4jGenerator
+//import com.flightfeather.uav.common.utils.DateUtil
+//import com.flightfeather.uav.common.utils.ObjToMapUtil
+//import com.flightfeather.uav.lightshare.bean.AreaVo
+//import org.springframework.stereotype.Component
+//import java.time.LocalDateTime
+//import java.time.format.DateTimeFormatter
+//import java.util.*
+//
+///**
+// * 璧拌埅瀛e害鎶ュ憡
+// * @date 2025/8/28 10:02
+// * @author feiyu
+// */
+//@Component
+//class UnderwaySeasonReport {
+//
+// fun generate() {
+// // 鍑嗗鏁版嵁妯″瀷
+// val summary = MissionSummary.Summary(
+// time = "2025骞寸涓夊搴︼紙7-9鏈堬級",
+// area = AreaVo().apply {
+// // 鍥哄畾琛屾斂鍖哄垝淇℃伅
+// provinceCode = "31"
+// provinceName = "涓婃捣甯�"
+// cityCode = "310100"
+// cityName = "涓婃捣甯�"
+// districtCode = "310101"
+// districtName = "榛勬郸鍖�"
+// townCode = "310101001"
+// townName = "澶栨哗琛楅亾"
+// // 鍥哄畾鍖哄煙缂栫爜鍜屽悕绉�
+// areaCode = "SH-HP-001"
+// area = "榛勬郸鍖鸿蛋鑸洃娴嬪尯鍩�"
+// // 鍥哄畾绠$悊鍏徃淇℃伅
+// managementCompanyId = "MC001"
+// managementCompany = "涓婃捣鐜鐩戞祴鏈夐檺鍏徃"
+// // 鍥哄畾鍦烘櫙绫诲瀷ID
+// sceneTypeId = "ST001"
+// },
+// count = 34,
+// kilometres = 256.8,
+// regionList = listOf("宸ヤ笟鍥尯A", "鍖栧伐鍖築", "灞呮皯鍖篊"),
+// countByDegree = listOf(
+// Triple("浼�", 15, 0.45), // 浼橈細15娆★紝鍗犳瘮45%
+// Triple("鑹�", 12, 0.36), // 鑹細12娆★紝鍗犳瘮36%
+// Triple("杞诲害姹℃煋", 5, 0.15),// 杞诲害姹℃煋锛�5娆★紝鍗犳瘮15%
+// Triple("涓害姹℃煋", 2, 0.04) // 涓害姹℃煋锛�2娆★紝鍗犳瘮4%
+// ),
+// probCount = 50,
+// highRiskSceneCount = 8,
+// probByFactor = listOf(
+// Triple("VOCs", 28, 0.56), // VOCs锛�28娆★紝鍗犳瘮56%
+// Triple("PM2.5", 12, 0.24), // PM2.5锛�12娆★紝鍗犳瘮24%
+// Triple("NO2", 7, 0.14), // NO2锛�7娆★紝鍗犳瘮14%
+// Triple("SO2", 3, 0.06) // SO2锛�3娆★紝鍗犳瘮6%
+// )
+// )
+// val dataModel = ObjToMapUtil.objectToMap(summary)
+//
+// // 鐢熸垚Word鏂囦欢
+// val timeStr = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())
+// Docx4jGenerator.generate(
+// templatePath = "underway_season_report.docx",
+// outputPath = "E:/idea/underway-spring/src/main/resources/templates/mission_summary_${timeStr}.docx",
+// dataModel = dataModel
+// )
+//
+// }
+//}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/common/file/Docx4jGenerator.kt b/src/main/kotlin/com/flightfeather/uav/common/file/Docx4jGenerator.kt
new file mode 100644
index 0000000..d33e105
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/common/file/Docx4jGenerator.kt
@@ -0,0 +1,114 @@
+package com.flightfeather.uav.common.file
+
+import freemarker.template.Configuration
+import freemarker.template.Template
+
+import org.docx4j.openpackaging.packages.WordprocessingMLPackage
+import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.OutputStreamWriter
+import java.nio.charset.StandardCharsets
+
+/**
+ * Word鏂囦欢鐢熸垚鍣紙鍩轰簬Docx4j + FreeMarker锛�
+ * @date 2025/8/28 09:29
+ * @author feiyu
+ */
+class Docx4jGenerator(
+ private val templatePath: String,
+ private val freemarkerConfig: Configuration = defaultFreemarkerConfig()
+) {
+ private var wordMLPackage: WordprocessingMLPackage? = null
+ private var mainDocumentPart: MainDocumentPart? = null
+
+ /**
+ * 鍒涘缓Word鏂囨。鍖�
+ */
+ fun loadTemplate(): Docx4jGenerator {
+ wordMLPackage = WordprocessingMLPackage.createPackage()
+ mainDocumentPart = wordMLPackage?.mainDocumentPart
+
+ return this
+ }
+
+ /**
+ * 浣跨敤FreeMarker濉厖妯℃澘鏁版嵁
+ */
+ fun fillData(dataModel: Map<String, Any>): Docx4jGenerator {
+ val template = freemarkerConfig.getTemplate(templatePath.substringAfterLast("/"))
+ val xmlContent = renderTemplate(template, dataModel)
+
+ mainDocumentPart?.unmarshal(ByteArrayInputStream(xmlContent.toByteArray(StandardCharsets.UTF_8)))
+ return this
+ }
+
+ /**
+ * 娣诲姞鍥剧墖鍒癢ord鏂囨。
+ * @param imagePath 鍥剧墖璺緞
+ * @param width 瀹藉害(鍍忕礌)
+ * @param height 楂樺害(鍍忕礌)
+ * @param paragraphId 娈佃惤ID锛屾寚瀹氬浘鐗囨彃鍏ヤ綅缃�
+ */
+ fun addImage(imagePath: String, width: Int, height: Int, paragraphId: String): Docx4jGenerator {
+ // 瀹炵幇鍥剧墖娣诲姞閫昏緫
+ return this
+ }
+
+ /**
+ * 娣诲姞琛ㄦ牸鍒癢ord鏂囨。
+ * @param data 琛ㄦ牸鏁版嵁
+ * @param paragraphId 娈佃惤ID锛屾寚瀹氳〃鏍兼彃鍏ヤ綅缃�
+ */
+ fun addTable(data: List<List<String>>, paragraphId: String): Docx4jGenerator {
+ // 瀹炵幇琛ㄦ牸娣诲姞閫昏緫
+ return this
+ }
+
+ /**
+ * 淇濆瓨鐢熸垚鐨刉ord鏂囦欢
+ * @param outputPath 杈撳嚭鏂囦欢璺緞
+ */
+ fun save(outputPath: String) {
+ wordMLPackage?.save(File(outputPath))
+ }
+
+ /**
+ * 浣跨敤FreeMarker娓叉煋妯℃澘
+ */
+ private fun renderTemplate(template: Template, dataModel: Map<String, Any>): String {
+ val outputStream = ByteArrayOutputStream()
+ val writer = OutputStreamWriter(outputStream, StandardCharsets.UTF_8)
+ template.process(dataModel, writer)
+ writer.flush()
+ return outputStream.toString(StandardCharsets.UTF_8.name())
+ }
+
+ companion object {
+ /**
+ * 榛樿FreeMarker閰嶇疆
+ */
+ fun defaultFreemarkerConfig(): Configuration {
+ val config = Configuration(Configuration.VERSION_2_3_31)
+ config.defaultEncoding = "UTF-8"
+ config.setClassForTemplateLoading(Docx4jGenerator::class.java, "/templates")
+ return config
+ }
+
+ /**
+ * 绠�鍖栬皟鐢ㄧ殑闈欐�佹柟娉�
+ */
+ fun generate(
+ templatePath: String,
+ outputPath: String,
+ dataModel: Map<String, Any>,
+ config: Configuration = defaultFreemarkerConfig()
+ ) {
+ Docx4jGenerator(templatePath, config)
+ .loadTemplate()
+ .fillData(dataModel)
+ .save(outputPath)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/common/utils/CommonUtil.kt b/src/main/kotlin/com/flightfeather/uav/common/utils/CommonUtil.kt
index e8ff31b..c958a6b 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/utils/CommonUtil.kt
+++ b/src/main/kotlin/com/flightfeather/uav/common/utils/CommonUtil.kt
@@ -17,4 +17,4 @@
return null
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/com/flightfeather/uav/common/utils/FileUtil.kt b/src/main/kotlin/com/flightfeather/uav/common/utils/FileUtil.kt
index 851cedf..a474c2e 100644
--- a/src/main/kotlin/com/flightfeather/uav/common/utils/FileUtil.kt
+++ b/src/main/kotlin/com/flightfeather/uav/common/utils/FileUtil.kt
@@ -65,12 +65,12 @@
// }
//鏂板缓杈撳嚭娴�
fw = FileWriter(file, true)
- bw = BufferedWriter(fw)
+ bw = fw?.let { BufferedWriter(it) }
}
//绗竴娆″啓鏂囨。鏃跺垵濮嬪寲杈撳嚭娴�
if (bw == null || fw == null) {
fw = FileWriter(file, true)
- bw = BufferedWriter(fw)
+ bw = fw?.let { BufferedWriter(it) }
}
bw?.run {
diff --git a/src/main/kotlin/com/flightfeather/uav/common/utils/ObjToMapUtil.kt b/src/main/kotlin/com/flightfeather/uav/common/utils/ObjToMapUtil.kt
new file mode 100644
index 0000000..4560884
--- /dev/null
+++ b/src/main/kotlin/com/flightfeather/uav/common/utils/ObjToMapUtil.kt
@@ -0,0 +1,62 @@
+package com.flightfeather.uav.common.utils
+
+import kotlin.reflect.KProperty1
+import kotlin.reflect.full.declaredMemberProperties
+import kotlin.reflect.jvm.isAccessible
+
+/**
+ * 瀵硅薄杞崲涓洪敭瀵瑰�糾ap缁撴瀯
+ * @date 2025/8/28 10:19
+ * @author feiyu
+ */
+object ObjToMapUtil {
+
+ /**
+ * 灏嗗璞¤浆鎹负Map缁撴瀯锛堟敮鎸佸祵濂楀璞¢�掑綊杞崲锛�
+ * @param obj 寰呰浆鎹㈢殑瀵硅薄锛堥潪绌猴級
+ * @return 鍖呭惈瀵硅薄灞炴�у悕鍜屽�肩殑Map锛屽祵濂楀璞′細杞崲涓篗ap锛岄泦鍚�/鏁扮粍浼氶�掑綊澶勭悊鍏冪礌
+ */
+ fun <T : Any> objectToMap(obj: T): Map<String, Any> {
+ return obj::class.declaredMemberProperties.associate { property ->
+ property.isAccessible = true
+ val value = try {
+ (property as KProperty1<T, *>).get(obj) } catch (e: Exception) { null }
+ property.name to processValue(value)
+ }
+ }
+
+ /**
+ * 閫掑綊澶勭悊灞炴�у�硷紝灏嗗祵濂楀璞¤浆鎹负Map锛岄泦鍚�/鏁扮粍閫掑綊澶勭悊鍏冪礌
+ */
+ private fun processValue(value: Any?): Any {
+ return when {
+ value == null -> ""
+ // 澶勭悊闆嗗悎绫诲瀷锛圠ist/Set锛�
+ value is Collection<*> -> value.map { processValue(it) }
+ // 澶勭悊Map绫诲瀷锛堣浆鎹㈠�硷級
+ value is Map<*, *> -> value.mapValues { processValue(it.value) }
+ // 澶勭悊鏁扮粍绫诲瀷
+ value.javaClass.isArray -> (value as Array<*>).map { processValue(it) }
+ // 澶勭悊鑷畾涔夊璞★紙閫掑綊杞崲锛�
+ isCustomObject(value) -> objectToMap(value)
+ // 鍩虹绫诲瀷锛圛nt/String/Boolean绛夛級鐩存帴杩斿洖
+ else -> value
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓洪渶瑕侀�掑綊杞崲鐨勮嚜瀹氫箟瀵硅薄
+ */
+ private fun isCustomObject(value: Any): Boolean {
+ val clazz = value::class.java
+ return !clazz.isPrimitive &&
+ !clazz.isEnum &&
+ value !is String &&
+ value !is Number &&
+ value !is Boolean &&
+ value !is Char &&
+ value !is Collection<*> &&
+ value !is Map<*, *> &&
+ !clazz.isArray
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt b/src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt
index 2540179..ce7cdec 100644
--- a/src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt
+++ b/src/main/kotlin/com/flightfeather/uav/domain/repository/MissionRep.kt
@@ -3,17 +3,45 @@
import com.flightfeather.uav.domain.entity.Mission
import com.flightfeather.uav.domain.mapper.MissionMapper
import org.springframework.stereotype.Repository
+import tk.mybatis.mapper.entity.Example
+import java.util.*
@Repository
class MissionRep(
private val missionMapper: MissionMapper,
) {
+ /**
+ * 鏍规嵁浠诲姟缂栫爜鏌ヨ鍗曚釜璧拌埅浠诲姟
+ * @param missionCode 浠诲姟缂栫爜锛堜富閿級
+ * @return 鏌ヨ鍒扮殑浠诲姟瀵硅薄锛岃嫢涓嶅瓨鍦ㄥ垯杩斿洖null
+ */
fun findOne(missionCode:String?): Mission? {
return missionMapper.selectByPrimaryKey(missionCode)
}
+ /**
+ * 鏍规嵁浠诲姟瀵硅薄灞炴�ф潯浠舵煡璇换鍔″垪琛�
+ * @param mission 鍖呭惈鏌ヨ鏉′欢鐨勪换鍔″璞★紙濡傝澶囩被鍨嬨�佸尯鍩熺瓑锛�
+ * @return 绗﹀悎鏉′欢鐨勪换鍔″垪琛�
+ */
fun findList(mission: Mission): List<Mission?> {
return missionMapper.select(mission)
}
+
+ /**
+ * 鏍规嵁鏃堕棿鑼冨洿鏌ヨ璧拌埅浠诲姟鍒楄〃
+ * @param startTime 鏌ヨ璧峰鏃堕棿锛堝寘鍚級
+ * @param endTime 鏌ヨ缁撴潫鏃堕棿锛堝寘鍚級
+ * @return 绗﹀悎鏃堕棿鏉′欢鐨勮蛋鑸换鍔″垪琛�
+ */
+ fun findByTimeRange(startTime: Date, endTime: Date): List<Mission?> {
+ // 浣跨敤tk.mybatis鐨凟xample鏋勫缓鏌ヨ鏉′欢
+ return missionMapper.selectByExample(Example(Mission::class.java).apply {
+ // 鍒涘缓鏌ヨ鏉′欢锛歴tartTime瀛楁鍦ㄦ寚瀹氭椂闂磋寖鍥村唴
+ createCriteria().andBetween("startTime", startTime, endTime)
+ // 杩囨护鎺夋病鏈夌粨鏉熸椂闂寸殑浠诲姟锛堢‘淇濅换鍔″凡瀹屾垚锛�
+ .andIsNotNull("endTime")
+ })
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImpl.kt b/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImpl.kt
index d00408f..c8ace2f 100644
--- a/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImpl.kt
+++ b/src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImpl.kt
@@ -93,6 +93,8 @@
val mission = missionRep.findOne(missionCode) ?: throw BizException("璧拌埅浠诲姟涓嶅瓨鍦�")
val data = realTimeDataRep.fetchData(mission)
mission.kilometres = MissionUtil.calKilometres(data).toFloat()
+ // todo: 璁$畻璧拌埅浠诲姟鎵�鍦ㄤ腑蹇冨尯鍩�
+ mission.region = MissionUtil.calRegion(data)
return updateMission(mission)
}
}
\ No newline at end of file
diff --git a/src/main/resources/templates/underway_season_report.docx b/src/main/resources/templates/underway_season_report.docx
new file mode 100644
index 0000000..de22f86
--- /dev/null
+++ b/src/main/resources/templates/underway_season_report.docx
Binary files differ
diff --git a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
index f7f4603..f21cc8c 100644
--- a/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
+++ b/src/test/kotlin/com/flightfeather/uav/lightshare/service/impl/MissionServiceImplTest.kt
@@ -2,22 +2,30 @@
import com.flightfeather.uav.biz.FactorFilter
import com.flightfeather.uav.biz.report.MissionReport
+import com.flightfeather.uav.common.exception.BizException
+import com.flightfeather.uav.domain.repository.MissionRep
import com.flightfeather.uav.lightshare.service.MissionService
-import org.junit.Test
-import org.junit.Assert.*
-import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
import javax.servlet.http.HttpServletResponse
-@RunWith(SpringRunner::class)
-@SpringBootTest
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.runner.RunWith
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+
class MissionServiceImplTest {
@Autowired
lateinit var missionService: MissionService
+
+ private var missionRep: MissionRep = mockk()
@Autowired
lateinit var missionReport: MissionReport
@@ -27,4 +35,67 @@
missionReport.execute("SH-CN-20240723-01", FactorFilter.default())
missionReport.execute("SH-CN-20240723-02", FactorFilter.default())
}
+
+ @Test
+ fun `calMissionInfo should throw BizException when mission not found`() {
+ // Arrange
+ val missionCode = "M001"
+ every { missionRep.findOne(missionCode) } returns null
+
+ // Act & Assert
+ val exception = assertThrows<BizException> {
+ missionService.calMissionInfo(missionCode)
+ }
+ assertEquals("璧拌埅浠诲姟涓嶅瓨鍦�", exception.message)
+ }
+
+ @Test
+ fun `calMissionInfo should calculate and update mission info successfully`() {
+ // Arrange
+ val missionCode = "M001"
+ val mission = Mission(missionCode)
+ val data = listOf<RealTimeData>()
+
+ every { missionRep.findOne(missionCode) } returns mission
+ every { realTimeDataRep.fetchData(mission) } returns data
+ every { missionUtil.calKilometres(data) } returns 100.0
+ every { missionUtil.calRegion(data) } returns "Center"
+ every { missionRep.updateMission(mission) } returns true
+
+ // Act
+ val result = missionService.calMissionInfo(missionCode)
+
+ // Assert
+ assertTrue(result)
+ assertEquals(100.0f, mission.kilometres)
+ assertEquals("Center", mission.region)
+
+ verify {
+ missionRep.findOne(missionCode)
+ realTimeDataRep.fetchData(mission)
+ missionUtil.calKilometres(data)
+ missionUtil.calRegion(data)
+ missionRep.updateMission(mission)
+ }
+ }
+
+ @Test
+ fun `calMissionInfo should return false when update fails`() {
+ // Arrange
+ val missionCode = "M001"
+ val mission = Mission(missionCode)
+ val data = listOf<RealTimeData>()
+
+ every { missionRep.findOne(missionCode) } returns mission
+ every { realTimeDataRep.fetchData(mission) } returns data
+ every { missionUtil.calKilometres(data) } returns 100.0
+ every { missionUtil.calRegion(data) } returns "Center"
+ every { missionRep.updateMission(mission) } returns false
+
+ // Act
+ val result = missionService.calMissionInfo(missionCode)
+
+ // Assert
+ assertFalse(result)
+ }
}
\ No newline at end of file
--
Gitblit v1.9.3