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