2025.8.28
1. 添加走航季度报告相关统计功能(待完成)
| | |
| | | <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> |
| | |
| | | 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 |
| | | |
| | | /** |
| | | * èµ°èªä»»å¡è®¡ç®å·¥å
· |
| | |
| | | */ |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®è½¨è¿¹ç¹è®¡ç®æå±åºåï¼ä¹¡é+è¡éï¼ |
| | | * @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 |
| | | // å°WGS84åæ è½¬æ¢ä¸ºGCJ02åæ åè¿è¡éå°çç¼ç è·åå°åä¿¡æ¯ |
| | | val address = AMapService.reGeo(MapUtil.wgs84ToGcj02(pair as Pair<Double, Double>)) |
| | | // è¿å乡éåè¡éåç§°ç»å |
| | | return address.township + address.street |
| | | } |
| | | } |
| | |
| | | */ |
| | | class MissionClue { |
| | | |
| | | inner class Clue{ |
| | | class Clue{ |
| | | var factor:FactorType?=null |
| | | var riskRegion:String?=null |
| | | } |
| | |
| | | class MissionInventory { |
| | | |
| | | // èµ°èªæ¸
åä¿¡æ¯ |
| | | inner class MissionInfo : Mission() { |
| | | class MissionInfo : Mission() { |
| | | // é¦è¦æ±¡æç© |
| | | var mainFactor: String? = null |
| | | |
| | |
| | | } |
| | | |
| | | // èµ°èªè¯¦æ
ä¿¡æ¯ |
| | | inner class MissionDetail : Mission() { |
| | | class MissionDetail : Mission() { |
| | | var keyScene: List<SceneInfo>? = null |
| | | var dataStatistics: List<FactorStatistics>? = null |
| | | |
| | |
| | | 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, |
| | | // æ»éç¨æ°ï¼å
¬éï¼ |
| | |
| | | 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) |
| | | } |
| | | } |
¶Ô±ÈÐÂÎļþ |
| | |
| | | //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.* |
| | | // |
| | | ///** |
| | | // * èµ°èªå£åº¦æ¥å |
| | | // * @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", "åå·¥åºB", "å±
æ°åºC"), |
| | | // 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 |
| | | // ) |
| | | // |
| | | // } |
| | | //} |
¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 |
| | | } |
| | | |
| | | /** |
| | | * æ·»å å¾çå°Wordææ¡£ |
| | | * @param imagePath å¾çè·¯å¾ |
| | | * @param width 宽度(åç´ ) |
| | | * @param height é«åº¦(åç´ ) |
| | | * @param paragraphId 段è½IDï¼æå®å¾çæå
¥ä½ç½® |
| | | */ |
| | | fun addImage(imagePath: String, width: Int, height: Int, paragraphId: String): Docx4jGenerator { |
| | | // å®ç°å¾çæ·»å é»è¾ |
| | | return this |
| | | } |
| | | |
| | | /** |
| | | * æ·»å è¡¨æ ¼å°Wordææ¡£ |
| | | * @param data è¡¨æ ¼æ°æ® |
| | | * @param paragraphId 段è½IDï¼æå®è¡¨æ ¼æå
¥ä½ç½® |
| | | */ |
| | | fun addTable(data: List<List<String>>, paragraphId: String): Docx4jGenerator { |
| | | // å®ç°è¡¨æ ¼æ·»å é»è¾ |
| | | return this |
| | | } |
| | | |
| | | /** |
| | | * ä¿åçæçWordæä»¶ |
| | | * @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) |
| | | } |
| | | } |
| | | } |
| | |
| | | // } |
| | | //æ°å»ºè¾åºæµ |
| | | 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 { |
¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.flightfeather.uav.common.utils |
| | | |
| | | import kotlin.reflect.KProperty1 |
| | | import kotlin.reflect.full.declaredMemberProperties |
| | | import kotlin.reflect.jvm.isAccessible |
| | | |
| | | /** |
| | | * 对象转æ¢ä¸ºé®å¯¹å¼mapç»æ |
| | | * @date 2025/8/28 10:19 |
| | | * @author feiyu |
| | | */ |
| | | object ObjToMapUtil { |
| | | |
| | | /** |
| | | * å°å¯¹è±¡è½¬æ¢ä¸ºMapç»æï¼æ¯æåµå¥å¯¹è±¡éå½è½¬æ¢ï¼ |
| | | * @param obj å¾
转æ¢ç对象ï¼éç©ºï¼ |
| | | * @return å
å«å¯¹è±¡å±æ§ååå¼çMapï¼åµå¥å¯¹è±¡ä¼è½¬æ¢ä¸ºMapï¼éå/æ°ç»ä¼éå½å¤çå
ç´ |
| | | */ |
| | | 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 -> "" |
| | | // å¤çéåç±»åï¼List/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) |
| | | // åºç¡ç±»åï¼Int/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 |
| | | } |
| | | } |
| | |
| | | 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çExampleæå»ºæ¥è¯¢æ¡ä»¶ |
| | | return missionMapper.selectByExample(Example(Mission::class.java).apply { |
| | | // å建æ¥è¯¢æ¡ä»¶ï¼startTimeåæ®µå¨æå®æ¶é´èå´å
|
| | | createCriteria().andBetween("startTime", startTime, endTime) |
| | | // è¿æ»¤ææ²¡æç»ææ¶é´çä»»å¡ï¼ç¡®ä¿ä»»å¡å·²å®æï¼ |
| | | .andIsNotNull("endTime") |
| | | }) |
| | | } |
| | | } |
| | |
| | | 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) |
| | | } |
| | | } |
| | |
| | | |
| | | 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 |
| | |
| | | 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) |
| | | } |
| | | } |