From d120479b125945750515631b7ddd08b4537c2ba5 Mon Sep 17 00:00:00 2001 From: hewencai <2357300448@qq.com> Date: Wed, 29 Oct 2025 17:34:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=AE=8C=E5=96=84=E8=AE=A1=E9=87=8F?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/UnitErrorCodeConstants.java | 9 + .../UnitConversionController.java | 51 +- .../vo/BatchUnitConvertByNameReqVO.java | 33 ++ .../vo/BatchUnitConvertBySymbolReqVO.java | 33 ++ .../vo/BatchUnitConvertReqVO.java | 19 + .../vo/BatchUnitConvertRespVO.java | 42 ++ .../vo/UnitConversionValidationRespVO.java | 110 ++++ .../vo/UnitConvertByNameReqVO.java | 28 + .../vo/UnitConvertBySymbolReqVO.java | 28 + .../UnitConversion/vo/UnitConvertReqVO.java | 26 + .../UnitConversion/vo/UnitConvertRespVO.java | 45 ++ .../unitQuantity/UnitQuantityController.java | 7 + .../vo/UnitQuantityTreeRespVO.java | 45 ++ .../UnitConversionHelperService.java | 239 ++++++++ .../UnitConversion/UnitConversionService.java | 56 ++ .../UnitConversionServiceImpl.java | 526 ++++++++++++++++++ .../unitQuantity/UnitQuantityService.java | 7 + .../unitQuantity/UnitQuantityServiceImpl.java | 57 ++ .../util/UnitConversionUtil.java | 407 ++++++++++++++ .../util/UnitConversionUtilTest.java | 264 +++++++++ 20 files changed, 2031 insertions(+), 1 deletion(-) create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertByNameReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertBySymbolReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertRespVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConversionValidationRespVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertByNameReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertBySymbolReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertReqVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertRespVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/vo/UnitQuantityTreeRespVO.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionHelperService.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtil.java create mode 100644 zt-module-unit-management/zt-module-unit-management-server/src/test/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtilTest.java diff --git a/zt-module-unit-management/zt-module-unit-management-api/src/main/java/com/zt/plat/module/unitmanagement/enums/UnitErrorCodeConstants.java b/zt-module-unit-management/zt-module-unit-management-api/src/main/java/com/zt/plat/module/unitmanagement/enums/UnitErrorCodeConstants.java index 696c09b..f9a92bf 100644 --- a/zt-module-unit-management/zt-module-unit-management-api/src/main/java/com/zt/plat/module/unitmanagement/enums/UnitErrorCodeConstants.java +++ b/zt-module-unit-management/zt-module-unit-management-api/src/main/java/com/zt/plat/module/unitmanagement/enums/UnitErrorCodeConstants.java @@ -19,4 +19,13 @@ public interface UnitErrorCodeConstants { ErrorCode UNT_INFO_NOT_EXISTS = new ErrorCode(1_010_000_004, "单位信息记录不存在"); + ErrorCode UNIT_NOT_FOUND = + new ErrorCode(1_010_000_005, "找不到单位: %s"); + + ErrorCode UNIT_CONVERSION_PATH_NOT_FOUND = + new ErrorCode(1_010_000_006, "无法找到从单位 [%s] 到单位 [%s] 的转换路径,请检查单位是否属于同一量纲或配置转换规则"); + + ErrorCode UNIT_DIFFERENT_QUANTITY = + new ErrorCode(1_010_000_007, "单位 [%s] 和单位 [%s] 不属于同一量纲,无法转换"); + } diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/UnitConversionController.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/UnitConversionController.java index c3ddbfe..8f5415f 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/UnitConversionController.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/UnitConversionController.java @@ -38,7 +38,6 @@ import com.zt.plat.module.unitmanagement.service.UnitConversion.UnitConversionSe @Validated public class UnitConversionController implements BusinessControllerMarker { - @Resource private UnitConversionService unitConversionService; @@ -105,4 +104,54 @@ public class UnitConversionController implements BusinessControllerMarker { BeanUtils.toBean(list, UnitConversionRespVO.class)); } + @PostMapping("/convert") + @Operation(summary = "单位转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult convert(@Valid @RequestBody UnitConvertReqVO convertReqVO) { + return success(unitConversionService.convert(convertReqVO)); + } + + @PostMapping("/batch-convert") + @Operation(summary = "批量单位转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult batchConvert(@Valid @RequestBody BatchUnitConvertReqVO batchReqVO) { + return success(unitConversionService.batchConvert(batchReqVO)); + } + + @PostMapping("/convert-by-symbol") + @Operation(summary = "按单位符号转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult convertBySymbol(@Valid @RequestBody UnitConvertBySymbolReqVO reqVO) { + return success(unitConversionService.convertBySymbol(reqVO)); + } + + @PostMapping("/convert-by-name") + @Operation(summary = "按单位名称转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult convertByName(@Valid @RequestBody UnitConvertByNameReqVO reqVO) { + return success(unitConversionService.convertByName(reqVO)); + } + + @PostMapping("/batch-convert-by-symbol") + @Operation(summary = "批量按单位符号转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult batchConvertBySymbol(@Valid @RequestBody BatchUnitConvertBySymbolReqVO reqVO) { + return success(unitConversionService.batchConvertBySymbol(reqVO)); + } + + @PostMapping("/batch-convert-by-name") + @Operation(summary = "批量按单位名称转换") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult batchConvertByName(@Valid @RequestBody BatchUnitConvertByNameReqVO reqVO) { + return success(unitConversionService.batchConvertByName(reqVO)); + } + + @GetMapping("/validate-paths") + @Operation(summary = "校验量纲的转换路径") + @Parameter(name = "quantityId", description = "量纲ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-conversion:query')") + public CommonResult validateConversionPaths(@RequestParam("quantityId") Long quantityId) { + return success(unitConversionService.validateConversionPaths(quantityId)); + } + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertByNameReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertByNameReqVO.java new file mode 100644 index 0000000..035d2f6 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertByNameReqVO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 批量按名称单位转换 Request VO") +@Data +public class BatchUnitConvertByNameReqVO { + + @Schema(description = "源单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "千克") + @NotBlank(message = "源单位名称不能为空") + private String srcUnitName; + + @Schema(description = "目标单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "吨") + @NotBlank(message = "目标单位名称不能为空") + private String tgtUnitName; + + @Schema(description = "待转换的值列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "待转换的值列表不能为空") + private List values; + + @Schema(description = "精度(小数位数)", example = "6") + private Integer precision = 6; + + @Schema(description = "是否忽略错误(true:遇到错误继续执行, false:遇到错误立即停止)", example = "false") + private Boolean ignoreErrors = false; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertBySymbolReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertBySymbolReqVO.java new file mode 100644 index 0000000..a0075e6 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertBySymbolReqVO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 批量按符号单位转换 Request VO") +@Data +public class BatchUnitConvertBySymbolReqVO { + + @Schema(description = "源单位符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "m") + @NotBlank(message = "源单位符号不能为空") + private String srcUnitSymbol; + + @Schema(description = "目标单位符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "km") + @NotBlank(message = "目标单位符号不能为空") + private String tgtUnitSymbol; + + @Schema(description = "待转换的值列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "待转换的值列表不能为空") + private List values; + + @Schema(description = "精度(小数位数)", example = "6") + private Integer precision = 6; + + @Schema(description = "是否忽略错误(true:遇到错误继续执行, false:遇到错误立即停止)", example = "false") + private Boolean ignoreErrors = false; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertReqVO.java new file mode 100644 index 0000000..477951e --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Schema(description = "管理后台 - 批量单位转换 Request VO") +@Data +public class BatchUnitConvertReqVO { + + @Schema(description = "转换项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "转换项列表不能为空") + private List items; + + @Schema(description = "是否忽略错误(true:遇到错误继续执行, false:遇到错误立即停止)", example = "false") + private Boolean ignoreErrors = false; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertRespVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertRespVO.java new file mode 100644 index 0000000..885df3d --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/BatchUnitConvertRespVO.java @@ -0,0 +1,42 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.Builder; +import java.util.List; + +@Schema(description = "管理后台 - 批量单位转换 Response VO") +@Data +@Builder +public class BatchUnitConvertRespVO { + + @Schema(description = "转换结果列表") + private List results; + + @Schema(description = "成功数量", example = "10") + private Integer successCount; + + @Schema(description = "失败数量", example = "0") + private Integer failureCount; + + @Schema(description = "总数量", example = "10") + private Integer totalCount; + + @Schema(description = "转换结果项") + @Data + @Builder + public static class UnitConvertResultItem { + + @Schema(description = "是否成功", example = "true") + private Boolean success; + + @Schema(description = "转换结果(成功时返回)") + private UnitConvertRespVO data; + + @Schema(description = "错误信息(失败时返回)", example = "找不到转换规则") + private String errorMessage; + + @Schema(description = "原始请求") + private UnitConvertReqVO request; + } +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConversionValidationRespVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConversionValidationRespVO.java new file mode 100644 index 0000000..e1c41d6 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConversionValidationRespVO.java @@ -0,0 +1,110 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Schema(description = "管理后台 - 单位转换路径校验响应 VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UnitConversionValidationRespVO { + + @Schema(description = "量纲ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long quantityId; + + @Schema(description = "量纲名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "长度") + private String quantityName; + + @Schema(description = "量纲符号", example = "L") + private String quantitySymbol; + + @Schema(description = "总单位数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8") + private Integer totalUnits; + + @Schema(description = "可转换单位数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8") + private Integer convertibleUnits; + + @Schema(description = "不可转换单位数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer unconvertibleUnits; + + @Schema(description = "是否全部可转换", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean allConvertible; + + @Schema(description = "基准单位信息") + private BaseUnitInfo baseUnit; + + @Schema(description = "不可转换的单位列表") + private List unconvertibleUnitList; + + @Schema(description = "转换路径详情") + private List conversionPaths; + + @Schema(description = "基准单位信息") + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class BaseUnitInfo { + @Schema(description = "单位ID", example = "1") + private Long unitId; + + @Schema(description = "单位名称", example = "米") + private String unitName; + + @Schema(description = "单位符号", example = "m") + private String unitSymbol; + } + + @Schema(description = "不可转换单位信息") + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class UnconvertibleUnitInfo { + @Schema(description = "单位ID", example = "1") + private Long unitId; + + @Schema(description = "单位名称", example = "光年") + private String unitName; + + @Schema(description = "单位符号", example = "ly") + private String unitSymbol; + + @Schema(description = "原因", example = "缺少到基准单位的转换规则") + private String reason; + } + + @Schema(description = "转换路径信息") + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ConversionPathInfo { + @Schema(description = "源单位ID", example = "1") + private Long srcUnitId; + + @Schema(description = "源单位名称", example = "千米") + private String srcUnitName; + + @Schema(description = "目标单位ID", example = "2") + private Long tgtUnitId; + + @Schema(description = "目标单位名称", example = "米") + private String tgtUnitName; + + @Schema(description = "是否有直接转换", example = "true") + private Boolean hasDirect; + + @Schema(description = "是否可通过基准单位转换", example = "true") + private Boolean hasViaBase; + + @Schema(description = "转换路径描述", example = "千米 → 米 (直接转换)") + private String pathDescription; + } +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertByNameReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertByNameReqVO.java new file mode 100644 index 0000000..ecf2959 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertByNameReqVO.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 按名称单位转换 Request VO") +@Data +public class UnitConvertByNameReqVO { + + @Schema(description = "源单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "千克") + @NotBlank(message = "源单位名称不能为空") + private String srcUnitName; + + @Schema(description = "目标单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "吨") + @NotBlank(message = "目标单位名称不能为空") + private String tgtUnitName; + + @Schema(description = "待转换的值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + @NotNull(message = "待转换的值不能为空") + private BigDecimal value; + + @Schema(description = "精度(小数位数)", example = "6") + private Integer precision = 6; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertBySymbolReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertBySymbolReqVO.java new file mode 100644 index 0000000..933e68e --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertBySymbolReqVO.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 按符号单位转换 Request VO") +@Data +public class UnitConvertBySymbolReqVO { + + @Schema(description = "源单位符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "kg") + @NotBlank(message = "源单位符号不能为空") + private String srcUnitSymbol; + + @Schema(description = "目标单位符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "t") + @NotBlank(message = "目标单位符号不能为空") + private String tgtUnitSymbol; + + @Schema(description = "待转换的值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + @NotNull(message = "待转换的值不能为空") + private BigDecimal value; + + @Schema(description = "精度(小数位数)", example = "6") + private Integer precision = 6; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertReqVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertReqVO.java new file mode 100644 index 0000000..3793d1e --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertReqVO.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 单位转换 Request VO") +@Data +public class UnitConvertReqVO { + + @Schema(description = "源单位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "源单位ID不能为空") + private Long srcUntId; + + @Schema(description = "目标单位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "目标单位ID不能为空") + private Long tgtUntId; + + @Schema(description = "待转换的值", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + @NotNull(message = "待转换的值不能为空") + private BigDecimal value; + + @Schema(description = "精度(小数位数)", example = "6") + private Integer precision = 6; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertRespVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertRespVO.java new file mode 100644 index 0000000..47a8915 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/UnitConversion/vo/UnitConvertRespVO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitConversion.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.Builder; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 单位转换 Response VO") +@Data +@Builder +public class UnitConvertRespVO { + + @Schema(description = "源单位ID", example = "1") + private Long srcUntId; + + @Schema(description = "源单位名称", example = "米") + private String srcUntName; + + @Schema(description = "源单位符号", example = "m") + private String srcUntSmb; + + @Schema(description = "目标单位ID", example = "2") + private Long tgtUntId; + + @Schema(description = "目标单位名称", example = "千米") + private String tgtUntName; + + @Schema(description = "目标单位符号", example = "km") + private String tgtUntSmb; + + @Schema(description = "原始值", example = "1000") + private BigDecimal originalValue; + + @Schema(description = "转换后的值", example = "1") + private BigDecimal convertedValue; + + @Schema(description = "转换因子", example = "0.001") + private BigDecimal factor; + + @Schema(description = "转换公式", example = "1000m = 1km") + private String formula; + + @Schema(description = "转换策略", example = "DIRECT") + private String strategy; +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/UnitQuantityController.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/UnitQuantityController.java index b9de3d6..f901754 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/UnitQuantityController.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/UnitQuantityController.java @@ -105,4 +105,11 @@ public class UnitQuantityController implements BusinessControllerMarker { BeanUtils.toBean(list, UnitQuantityRespVO.class)); } + @GetMapping("/tree") + @Operation(summary = "获取量纲及单位树") + @PreAuthorize("@ss.hasPermission('unitmanagement:unit-quantity:query')") + public CommonResult> getUnitQuantityTree() { + return success(unitQuantityService.getUnitQuantityTree()); + } + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/vo/UnitQuantityTreeRespVO.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/vo/UnitQuantityTreeRespVO.java new file mode 100644 index 0000000..3b1cc2e --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/controller/admin/unitQuantity/vo/UnitQuantityTreeRespVO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.unitmanagement.controller.admin.UnitQuantity.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import java.util.List; + +@Schema(description = "管理后台 - 量纲及单位树 Response VO") +@Data +public class UnitQuantityTreeRespVO { + + @Schema(description = "量纲ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "量纲名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "长度") + private String name; + + @Schema(description = "量纲符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "L") + private String symbol; + + @Schema(description = "量纲描述", example = "用于测量物体长短、距离的物理量") + private String dsp; + + @Schema(description = "下属单位列表") + private List units; + + @Schema(description = "单位项") + @Data + public static class UnitItemVO { + + @Schema(description = "单位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "米") + private String name; + + @Schema(description = "单位符号", requiredMode = Schema.RequiredMode.REQUIRED, example = "m") + private String smb; + + @Schema(description = "是否基准单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer isBse; + + @Schema(description = "关联关系ID", example = "1") + private Long relationId; + } +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionHelperService.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionHelperService.java new file mode 100644 index 0000000..62b9da5 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionHelperService.java @@ -0,0 +1,239 @@ +package com.zt.plat.module.unitmanagement.service.UnitConversion; + +import com.zt.plat.module.unitmanagement.dal.dao.UnitConversion.UnitConversionMapper; +import com.zt.plat.module.unitmanagement.dal.dao.QuantityUnitRelation.QuantityUnitRelationMapper; +import com.zt.plat.module.unitmanagement.dal.dao.UntInfo.UntInfoMapper; +import com.zt.plat.module.unitmanagement.dal.dataobject.UnitConversion.UnitConversionDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.QuantityUnitRelation.QuantityUnitRelationDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.UntInfo.UntInfoDO; +import com.zt.plat.module.unitmanagement.util.UnitConversionUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.math.BigDecimal; +import java.util.List; + +/** + * 单位转换辅助服务 + * + * 为其他模块提供简单易用的单位转换方法 + * 支持按单位符号和单位名称进行转换 + * + * @author 系统 + */ +@Service +@Slf4j +public class UnitConversionHelperService { + + @Resource + private UnitConversionMapper unitConversionMapper; + + @Resource + private QuantityUnitRelationMapper quantityUnitRelationMapper; + + @Resource + private UntInfoMapper untInfoMapper; + + /** + * 按单位符号转换(推荐使用) + * + * @param value 待转换的值 + * @param srcSymbol 源单位符号(如: "kg", "m", "km") + * @param tgtSymbol 目标单位符号 + * @return 转换后的值 + */ + public BigDecimal convertBySymbol(BigDecimal value, String srcSymbol, String tgtSymbol) { + return convertBySymbol(value, srcSymbol, tgtSymbol, 6); + } + + /** + * 按单位符号转换(推荐使用) + * + * @param value 待转换的值 + * @param srcSymbol 源单位符号(如: "kg", "m", "km") + * @param tgtSymbol 目标单位符号 + * @param precision 精度(小数位数) + * @return 转换后的值 + */ + public BigDecimal convertBySymbol(BigDecimal value, String srcSymbol, String tgtSymbol, int precision) { + log.debug("按符号转换: {} {} → {}", value, srcSymbol, tgtSymbol); + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertBySymbol( + value, srcSymbol, tgtSymbol, conversions, relations, units, precision + ); + + log.debug("转换结果: {} (策略: {})", result.getValue(), result.getStrategy()); + return result.getValue(); + } + + /** + * 按单位名称转换 + * + * @param value 待转换的值 + * @param srcName 源单位名称(如: "千克", "米", "千米") + * @param tgtName 目标单位名称 + * @return 转换后的值 + */ + public BigDecimal convertByName(BigDecimal value, String srcName, String tgtName) { + return convertByName(value, srcName, tgtName, 6); + } + + /** + * 按单位名称转换 + * + * @param value 待转换的值 + * @param srcName 源单位名称(如: "千克", "米", "千米") + * @param tgtName 目标单位名称 + * @param precision 精度(小数位数) + * @return 转换后的值 + */ + public BigDecimal convertByName(BigDecimal value, String srcName, String tgtName, int precision) { + log.debug("按名称转换: {} {} → {}", value, srcName, tgtName); + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertByName( + value, srcName, tgtName, conversions, relations, units, precision + ); + + log.debug("转换结果: {} (策略: {})", result.getValue(), result.getStrategy()); + return result.getValue(); + } + + /** + * 按单位符号批量转换 + * + * @param values 待转换的值列表 + * @param srcSymbol 源单位符号 + * @param tgtSymbol 目标单位符号 + * @return 转换后的值列表 + */ + public List batchConvertBySymbol(List values, String srcSymbol, String tgtSymbol) { + return batchConvertBySymbol(values, srcSymbol, tgtSymbol, 6); + } + + /** + * 按单位符号批量转换 + * + * @param values 待转换的值列表 + * @param srcSymbol 源单位符号 + * @param tgtSymbol 目标单位符号 + * @param precision 精度(小数位数) + * @return 转换后的值列表 + */ + public List batchConvertBySymbol(List values, String srcSymbol, String tgtSymbol, int precision) { + log.debug("批量按符号转换: {} 个值, {} → {}", values.size(), srcSymbol, tgtSymbol); + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + List results = UnitConversionUtil.batchConvertBySymbol( + values, srcSymbol, tgtSymbol, conversions, relations, units, precision + ); + + // 提取转换后的值 + return results.stream() + .map(UnitConversionUtil.ConversionResult::getValue) + .toList(); + } + + /** + * 按单位名称批量转换 + * + * @param values 待转换的值列表 + * @param srcName 源单位名称 + * @param tgtName 目标单位名称 + * @return 转换后的值列表 + */ + public List batchConvertByName(List values, String srcName, String tgtName) { + return batchConvertByName(values, srcName, tgtName, 6); + } + + /** + * 按单位名称批量转换 + * + * @param values 待转换的值列表 + * @param srcName 源单位名称 + * @param tgtName 目标单位名称 + * @param precision 精度(小数位数) + * @return 转换后的值列表 + */ + public List batchConvertByName(List values, String srcName, String tgtName, int precision) { + log.debug("批量按名称转换: {} 个值, {} → {}", values.size(), srcName, tgtName); + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + List results = UnitConversionUtil.batchConvertByName( + values, srcName, tgtName, conversions, relations, units, precision + ); + + // 提取转换后的值 + return results.stream() + .map(UnitConversionUtil.ConversionResult::getValue) + .toList(); + } + + /** + * 按单位符号转换(返回完整结果) + * + * @param value 待转换的值 + * @param srcSymbol 源单位符号 + * @param tgtSymbol 目标单位符号 + * @param precision 精度(小数位数) + * @return 转换结果(包含值、因子、策略等) + */ + public UnitConversionUtil.ConversionResult convertBySymbolWithDetails( + BigDecimal value, String srcSymbol, String tgtSymbol, int precision) { + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + return UnitConversionUtil.convertBySymbol( + value, srcSymbol, tgtSymbol, conversions, relations, units, precision + ); + } + + /** + * 按单位名称转换(返回完整结果) + * + * @param value 待转换的值 + * @param srcName 源单位名称 + * @param tgtName 目标单位名称 + * @param precision 精度(小数位数) + * @return 转换结果(包含值、因子、策略等) + */ + public UnitConversionUtil.ConversionResult convertByNameWithDetails( + BigDecimal value, String srcName, String tgtName, int precision) { + + // 加载数据 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 调用工具类 + return UnitConversionUtil.convertByName( + value, srcName, tgtName, conversions, relations, units, precision + ); + } +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionService.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionService.java index c5edb6d..67fdd2d 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionService.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionService.java @@ -59,4 +59,60 @@ public interface UnitConversionService { */ PageResult getUnitConversionPage(UnitConversionPageReqVO pageReqVO); + /** + * 单位转换 + * + * @param convertReqVO 转换请求 + * @return 转换结果 + */ + UnitConvertRespVO convert(@Valid UnitConvertReqVO convertReqVO); + + /** + * 批量单位转换 + * + * @param batchReqVO 批量转换请求 + * @return 批量转换结果 + */ + BatchUnitConvertRespVO batchConvert(@Valid BatchUnitConvertReqVO batchReqVO); + + /** + * 按单位符号转换 + * + * @param reqVO 转换请求 + * @return 转换结果 + */ + UnitConvertRespVO convertBySymbol(@Valid UnitConvertBySymbolReqVO reqVO); + + /** + * 按单位名称转换 + * + * @param reqVO 转换请求 + * @return 转换结果 + */ + UnitConvertRespVO convertByName(@Valid UnitConvertByNameReqVO reqVO); + + /** + * 批量按单位符号转换 + * + * @param reqVO 批量转换请求 + * @return 批量转换结果 + */ + BatchUnitConvertRespVO batchConvertBySymbol(@Valid BatchUnitConvertBySymbolReqVO reqVO); + + /** + * 批量按单位名称转换 + * + * @param reqVO 批量转换请求 + * @return 批量转换结果 + */ + BatchUnitConvertRespVO batchConvertByName(@Valid BatchUnitConvertByNameReqVO reqVO); + + /** + * 校验量纲内所有单位的转换路径 + * + * @param quantityId 量纲ID + * @return 校验结果 + */ + UnitConversionValidationRespVO validateConversionPaths(Long quantityId); + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionServiceImpl.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionServiceImpl.java index 23adef1..7507803 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionServiceImpl.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/UnitConversion/UnitConversionServiceImpl.java @@ -14,6 +14,18 @@ import com.zt.plat.framework.common.pojo.PageParam; import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.unitmanagement.dal.dao.UnitConversion.UnitConversionMapper; +import com.zt.plat.module.unitmanagement.dal.dao.QuantityUnitRelation.QuantityUnitRelationMapper; +import com.zt.plat.module.unitmanagement.dal.dao.UntInfo.UntInfoMapper; +import com.zt.plat.module.unitmanagement.dal.dataobject.QuantityUnitRelation.QuantityUnitRelationDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.UntInfo.UntInfoDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.UnitQuantity.UnitQuantityDO; +import com.zt.plat.module.unitmanagement.service.UnitQuantity.UnitQuantityService; +import com.zt.plat.module.unitmanagement.util.UnitConversionUtil; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; +import java.util.stream.Collectors; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; @@ -27,11 +39,21 @@ import static com.zt.plat.module.unitmanagement.enums.UnitErrorCodeConstants.*; */ @Service @Validated +@Slf4j public class UnitConversionServiceImpl implements UnitConversionService { @Resource private UnitConversionMapper unitConversionMapper; + @Resource + private QuantityUnitRelationMapper quantityUnitRelationMapper; + + @Resource + private UntInfoMapper untInfoMapper; + + @Resource + private UnitQuantityService unitQuantityService; + @Override public UnitConversionRespVO createUnitConversion(UnitConversionSaveReqVO createReqVO) { // 插入 @@ -89,4 +111,508 @@ public class UnitConversionServiceImpl implements UnitConversionService { return unitConversionMapper.selectPage(pageReqVO); } + @Override + public UnitConvertRespVO convert(UnitConvertReqVO convertReqVO) { + log.info("开始单位转换: 源单位ID={}, 目标单位ID={}, 值={}", + convertReqVO.getSrcUntId(), convertReqVO.getTgtUntId(), convertReqVO.getValue()); + + // 1. 查询所有转换规则和关联关系 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + + // 2. 使用工具类进行转换 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + convertReqVO.getValue(), + convertReqVO.getSrcUntId(), + convertReqVO.getTgtUntId(), + conversions, + relations, + convertReqVO.getPrecision() != null ? convertReqVO.getPrecision() : 6 + ); + + // 3. 查询单位信息 + UntInfoDO srcUnit = untInfoMapper.selectById(convertReqVO.getSrcUntId()); + UntInfoDO tgtUnit = untInfoMapper.selectById(convertReqVO.getTgtUntId()); + + // 4. 构建响应 + return UnitConvertRespVO.builder() + .srcUntId(convertReqVO.getSrcUntId()) + .srcUntName(srcUnit != null ? srcUnit.getName() : null) + .srcUntSmb(srcUnit != null ? srcUnit.getSmb() : null) + .tgtUntId(convertReqVO.getTgtUntId()) + .tgtUntName(tgtUnit != null ? tgtUnit.getName() : null) + .tgtUntSmb(tgtUnit != null ? tgtUnit.getSmb() : null) + .originalValue(convertReqVO.getValue()) + .convertedValue(result.getValue()) + .factor(result.getFactor()) + .formula(result.getFormula()) + .strategy(result.getStrategy().name()) + .build(); + } + + @Override + public BatchUnitConvertRespVO batchConvert(BatchUnitConvertReqVO batchReqVO) { + log.info("开始批量单位转换: 转换项数量={}, 忽略错误={}", + batchReqVO.getItems().size(), batchReqVO.getIgnoreErrors()); + + List results = new ArrayList<>(); + int successCount = 0; + int failureCount = 0; + + for (UnitConvertReqVO item : batchReqVO.getItems()) { + try { + // 执行转换 + UnitConvertRespVO convertResult = convert(item); + + // 构建成功结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(true) + .data(convertResult) + .request(item) + .build()); + + successCount++; + } catch (Exception e) { + log.warn("单位转换失败: 源单位ID={}, 目标单位ID={}, 错误={}", + item.getSrcUntId(), item.getTgtUntId(), e.getMessage()); + + // 构建失败结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(false) + .errorMessage(e.getMessage()) + .request(item) + .build()); + + failureCount++; + + // 如果不忽略错误,直接抛出异常 + if (!Boolean.TRUE.equals(batchReqVO.getIgnoreErrors())) { + throw e; + } + } + } + + return BatchUnitConvertRespVO.builder() + .results(results) + .successCount(successCount) + .failureCount(failureCount) + .totalCount(batchReqVO.getItems().size()) + .build(); + } + + @Override + public UnitConvertRespVO convertBySymbol(UnitConvertBySymbolReqVO reqVO) { + log.info("开始按符号转换: 源单位符号={}, 目标单位符号={}, 值={}", + reqVO.getSrcUnitSymbol(), reqVO.getTgtUnitSymbol(), reqVO.getValue()); + + // 1. 查询所有转换规则、关联关系和单位信息 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 2. 使用工具类进行转换 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertBySymbol( + reqVO.getValue(), + reqVO.getSrcUnitSymbol(), + reqVO.getTgtUnitSymbol(), + conversions, + relations, + units, + reqVO.getPrecision() != null ? reqVO.getPrecision() : 6 + ); + + // 3. 构建响应 + return UnitConvertRespVO.builder() + .srcUntName(reqVO.getSrcUnitSymbol()) + .srcUntSmb(reqVO.getSrcUnitSymbol()) + .tgtUntName(reqVO.getTgtUnitSymbol()) + .tgtUntSmb(reqVO.getTgtUnitSymbol()) + .originalValue(reqVO.getValue()) + .convertedValue(result.getValue()) + .factor(result.getFactor()) + .formula(result.getFormula()) + .strategy(result.getStrategy().name()) + .build(); + } + + @Override + public UnitConvertRespVO convertByName(UnitConvertByNameReqVO reqVO) { + log.info("开始按名称转换: 源单位名称={}, 目标单位名称={}, 值={}", + reqVO.getSrcUnitName(), reqVO.getTgtUnitName(), reqVO.getValue()); + + // 1. 查询所有转换规则、关联关系和单位信息 + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + // 2. 使用工具类进行转换 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertByName( + reqVO.getValue(), + reqVO.getSrcUnitName(), + reqVO.getTgtUnitName(), + conversions, + relations, + units, + reqVO.getPrecision() != null ? reqVO.getPrecision() : 6 + ); + + // 3. 构建响应 + return UnitConvertRespVO.builder() + .srcUntName(reqVO.getSrcUnitName()) + .tgtUntName(reqVO.getTgtUnitName()) + .originalValue(reqVO.getValue()) + .convertedValue(result.getValue()) + .factor(result.getFactor()) + .formula(result.getFormula()) + .strategy(result.getStrategy().name()) + .build(); + } + + @Override + public BatchUnitConvertRespVO batchConvertBySymbol(BatchUnitConvertBySymbolReqVO reqVO) { + log.info("开始批量按符号转换: 源单位符号={}, 目标单位符号={}, 值数量={}", + reqVO.getSrcUnitSymbol(), reqVO.getTgtUnitSymbol(), reqVO.getValues().size()); + + List results = new ArrayList<>(); + int successCount = 0; + int failureCount = 0; + + // 1. 查询所有转换规则、关联关系和单位信息(只查询一次) + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + for (BigDecimal value : reqVO.getValues()) { + try { + // 2. 使用工具类进行转换 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertBySymbol( + value, + reqVO.getSrcUnitSymbol(), + reqVO.getTgtUnitSymbol(), + conversions, + relations, + units, + reqVO.getPrecision() != null ? reqVO.getPrecision() : 6 + ); + + // 3. 构建成功结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(true) + .data(UnitConvertRespVO.builder() + .srcUntSmb(reqVO.getSrcUnitSymbol()) + .tgtUntSmb(reqVO.getTgtUnitSymbol()) + .originalValue(value) + .convertedValue(result.getValue()) + .factor(result.getFactor()) + .strategy(result.getStrategy().name()) + .build()) + .build()); + successCount++; + } catch (Exception e) { + log.warn("按符号转换失败: 源单位符号={}, 目标单位符号={}, 值={}, 错误={}", + reqVO.getSrcUnitSymbol(), reqVO.getTgtUnitSymbol(), value, e.getMessage()); + + // 4. 构建失败结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(false) + .errorMessage(e.getMessage()) + .build()); + failureCount++; + + // 5. 如果不忽略错误,直接抛出异常 + if (!Boolean.TRUE.equals(reqVO.getIgnoreErrors())) { + throw e; + } + } + } + + return BatchUnitConvertRespVO.builder() + .results(results) + .successCount(successCount) + .failureCount(failureCount) + .totalCount(reqVO.getValues().size()) + .build(); + } + + @Override + public BatchUnitConvertRespVO batchConvertByName(BatchUnitConvertByNameReqVO reqVO) { + log.info("开始批量按名称转换: 源单位名称={}, 目标单位名称={}, 值数量={}", + reqVO.getSrcUnitName(), reqVO.getTgtUnitName(), reqVO.getValues().size()); + + List results = new ArrayList<>(); + int successCount = 0; + int failureCount = 0; + + // 1. 查询所有转换规则、关联关系和单位信息(只查询一次) + List conversions = unitConversionMapper.selectList(); + List relations = quantityUnitRelationMapper.selectList(); + List units = untInfoMapper.selectList(); + + for (BigDecimal value : reqVO.getValues()) { + try { + // 2. 使用工具类进行转换 + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convertByName( + value, + reqVO.getSrcUnitName(), + reqVO.getTgtUnitName(), + conversions, + relations, + units, + reqVO.getPrecision() != null ? reqVO.getPrecision() : 6 + ); + + // 3. 构建成功结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(true) + .data(UnitConvertRespVO.builder() + .srcUntName(reqVO.getSrcUnitName()) + .tgtUntName(reqVO.getTgtUnitName()) + .originalValue(value) + .convertedValue(result.getValue()) + .factor(result.getFactor()) + .strategy(result.getStrategy().name()) + .build()) + .build()); + successCount++; + } catch (Exception e) { + log.warn("按名称转换失败: 源单位名称={}, 目标单位名称={}, 值={}, 错误={}", + reqVO.getSrcUnitName(), reqVO.getTgtUnitName(), value, e.getMessage()); + + // 4. 构建失败结果 + results.add(BatchUnitConvertRespVO.UnitConvertResultItem.builder() + .success(false) + .errorMessage(e.getMessage()) + .build()); + failureCount++; + + // 5. 如果不忽略错误,直接抛出异常 + if (!Boolean.TRUE.equals(reqVO.getIgnoreErrors())) { + throw e; + } + } + } + + return BatchUnitConvertRespVO.builder() + .results(results) + .successCount(successCount) + .failureCount(failureCount) + .totalCount(reqVO.getValues().size()) + .build(); + } + + @Override + public UnitConversionValidationRespVO validateConversionPaths(Long quantityId) { + log.info("开始校验量纲 {} 的转换路径", quantityId); + + // 1. 查询量纲信息 + UnitQuantityDO quantity = unitQuantityService.getUnitQuantity(quantityId); + if (quantity == null) { + throw exception(UNIT_QUANTITY_NOT_EXISTS); + } + + // 2. 查询该量纲下的所有单位 + List relations = quantityUnitRelationMapper.selectList( + new LambdaQueryWrapperX() + .eq(QuantityUnitRelationDO::getUntQtyId, quantityId) + ); + + if (CollUtil.isEmpty(relations)) { + return buildEmptyValidationResult(quantityId, quantity); + } + + // 3. 获取单位详细信息 + List unitIds = relations.stream() + .map(QuantityUnitRelationDO::getUntId) + .collect(Collectors.toList()); + List units = untInfoMapper.selectByIds(unitIds); + Map unitMap = units.stream() + .collect(Collectors.toMap(UntInfoDO::getId, u -> u)); + + // 4. 找到基准单位 + QuantityUnitRelationDO baseRelation = relations.stream() + .filter(r -> r.getIsBse() == 1) + .findFirst() + .orElse(null); + + UnitConversionValidationRespVO.BaseUnitInfo baseUnitInfo = null; + Long baseUnitId = null; + if (baseRelation != null) { + UntInfoDO baseUnit = unitMap.get(baseRelation.getUntId()); + baseUnitId = baseRelation.getUntId(); + if (baseUnit != null) { + baseUnitInfo = UnitConversionValidationRespVO.BaseUnitInfo.builder() + .unitId(baseUnit.getId()) + .unitName(baseUnit.getName()) + .unitSymbol(baseUnit.getSmb()) + .build(); + } + } + + // 5. 查询该量纲下的所有转换规则 + List conversions = unitConversionMapper.selectList( + new LambdaQueryWrapperX() + .eq(UnitConversionDO::getUntQtyId, quantityId) + ); + + // 6. 检查所有单位对之间的转换路径(两两互相转换) + List unconvertibleUnits = new ArrayList<>(); + List conversionPaths = new ArrayList<>(); + + // 统计:能够互相转换的单位数量 + Set fullyConvertibleUnits = new HashSet<>(); + + // 遍历所有单位对 + for (int i = 0; i < units.size(); i++) { + UntInfoDO srcUnit = units.get(i); + boolean canConvertToAll = true; + + for (int j = 0; j < units.size(); j++) { + if (i == j) continue; // 跳过自己 + + UntInfoDO tgtUnit = units.get(j); + + // 尝试转换(使用工具类) + boolean canConvert = canConvert(srcUnit.getId(), tgtUnit.getId(), conversions, relations, baseUnitId); + + if (!canConvert) { + canConvertToAll = false; + // 只记录第一个方向的失败(避免重复:A→B 和 B→A) + if (i < j) { + unconvertibleUnits.add(UnitConversionValidationRespVO.UnconvertibleUnitInfo.builder() + .unitId(srcUnit.getId()) + .unitName(srcUnit.getName()) + .unitSymbol(srcUnit.getSmb()) + .reason(String.format("无法转换到 %s", tgtUnit.getName())) + .build()); + } + } else { + // 记录转换路径(只记录到基准单位的路径,避免太多) + if (baseUnitId != null && tgtUnit.getId().equals(baseUnitId)) { + String strategy = getConversionStrategy(srcUnit.getId(), tgtUnit.getId(), conversions, baseUnitId); + conversionPaths.add(UnitConversionValidationRespVO.ConversionPathInfo.builder() + .srcUnitId(srcUnit.getId()) + .srcUnitName(srcUnit.getName()) + .tgtUnitId(tgtUnit.getId()) + .tgtUnitName(tgtUnit.getName()) + .hasDirect(strategy.equals("DIRECT")) + .hasViaBase(strategy.equals("VIA_BASE_UNIT")) + .pathDescription(srcUnit.getName() + " → " + tgtUnit.getName() + + (strategy.equals("DIRECT") ? " (直接转换)" : " (通过基准单位)")) + .build()); + } + } + } + + if (canConvertToAll) { + fullyConvertibleUnits.add(srcUnit.getId()); + } + } + + // 7. 构建响应 + return UnitConversionValidationRespVO.builder() + .quantityId(quantityId) + .quantityName(quantity.getName()) + .quantitySymbol(quantity.getSymbol()) + .totalUnits(units.size()) + .convertibleUnits(fullyConvertibleUnits.size()) + .unconvertibleUnits(units.size() - fullyConvertibleUnits.size()) + .allConvertible(fullyConvertibleUnits.size() == units.size()) + .baseUnit(baseUnitInfo) + .unconvertibleUnitList(unconvertibleUnits) + .conversionPaths(conversionPaths) + .build(); + } + + /** + * 检查两个单位是否能够转换(考虑反向推导) + */ + private boolean canConvert(Long srcUnitId, Long tgtUnitId, + List conversions, + List relations, + Long baseUnitId) { + // 1. 检查直接转换(正向) + boolean hasDirectForward = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(srcUnitId) && c.getTgtUntId().equals(tgtUnitId)); + if (hasDirectForward) { + return true; + } + + // 2. 检查直接转换(反向,可以推导) + boolean hasDirectReverse = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(tgtUnitId) && c.getTgtUntId().equals(srcUnitId)); + if (hasDirectReverse) { + return true; + } + + // 3. 检查通过基准单位的间接转换 + if (baseUnitId == null) { + return false; + } + + // 如果源单位是基准单位 + if (srcUnitId.equals(baseUnitId)) { + // 检查是否有 基准 → 目标 的转换规则(正向或反向) + boolean hasBaseToTgt = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(baseUnitId) && c.getTgtUntId().equals(tgtUnitId)); + boolean hasTgtToBase = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(tgtUnitId) && c.getTgtUntId().equals(baseUnitId)); + return hasBaseToTgt || hasTgtToBase; + } + + // 如果目标单位是基准单位 + if (tgtUnitId.equals(baseUnitId)) { + // 检查是否有 源 → 基准 的转换规则(正向或反向) + boolean hasSrcToBase = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(srcUnitId) && c.getTgtUntId().equals(baseUnitId)); + boolean hasBaseToSrc = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(baseUnitId) && c.getTgtUntId().equals(srcUnitId)); + return hasSrcToBase || hasBaseToSrc; + } + + // 两者都不是基准单位,需要两步转换:源 → 基准 → 目标 + // 检查 源 → 基准(正向或反向) + boolean srcToBase = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(srcUnitId) && c.getTgtUntId().equals(baseUnitId)); + boolean baseToSrc = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(baseUnitId) && c.getTgtUntId().equals(srcUnitId)); + + // 检查 基准 → 目标(正向或反向) + boolean baseToTgt = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(baseUnitId) && c.getTgtUntId().equals(tgtUnitId)); + boolean tgtToBase = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(tgtUnitId) && c.getTgtUntId().equals(baseUnitId)); + + return (srcToBase || baseToSrc) && (baseToTgt || tgtToBase); + } + + /** + * 获取转换策略 + */ + private String getConversionStrategy(Long srcUnitId, Long tgtUnitId, + List conversions, + Long baseUnitId) { + // 检查直接转换 + boolean hasDirect = conversions.stream() + .anyMatch(c -> c.getSrcUntId().equals(srcUnitId) && c.getTgtUntId().equals(tgtUnitId)); + if (hasDirect) { + return "DIRECT"; + } + return "VIA_BASE_UNIT"; + } + + private UnitConversionValidationRespVO buildEmptyValidationResult(Long quantityId, UnitQuantityDO quantity) { + return UnitConversionValidationRespVO.builder() + .quantityId(quantityId) + .quantityName(quantity.getName()) + .quantitySymbol(quantity.getSymbol()) + .totalUnits(0) + .convertibleUnits(0) + .unconvertibleUnits(0) + .allConvertible(true) + .baseUnit(null) + .unconvertibleUnitList(new ArrayList<>()) + .conversionPaths(new ArrayList<>()) + .build(); + } + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityService.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityService.java index 3000f82..ef5c4ca 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityService.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityService.java @@ -59,4 +59,11 @@ public interface UnitQuantityService { */ PageResult getUnitQuantityPage(UnitQuantityPageReqVO pageReqVO); + /** + * 获取量纲及单位树 + * + * @return 量纲及单位树列表 + */ + List getUnitQuantityTree(); + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityServiceImpl.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityServiceImpl.java index ff72621..5a482c3 100644 --- a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityServiceImpl.java +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/service/unitQuantity/UnitQuantityServiceImpl.java @@ -14,6 +14,11 @@ import com.zt.plat.framework.common.pojo.PageParam; import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.unitmanagement.dal.dao.UnitQuantity.UnitQuantityMapper; +import com.zt.plat.module.unitmanagement.dal.dao.QuantityUnitRelation.QuantityUnitRelationMapper; +import com.zt.plat.module.unitmanagement.dal.dao.UntInfo.UntInfoMapper; +import com.zt.plat.module.unitmanagement.dal.dataobject.QuantityUnitRelation.QuantityUnitRelationDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.UntInfo.UntInfoDO; +import java.util.stream.Collectors; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList; @@ -32,6 +37,12 @@ public class UnitQuantityServiceImpl implements UnitQuantityService { @Resource private UnitQuantityMapper unitQuantityMapper; + @Resource + private QuantityUnitRelationMapper quantityUnitRelationMapper; + + @Resource + private UntInfoMapper untInfoMapper; + @Override public UnitQuantityRespVO createUnitQuantity(UnitQuantitySaveReqVO createReqVO) { // 插入 @@ -89,4 +100,50 @@ public class UnitQuantityServiceImpl implements UnitQuantityService { return unitQuantityMapper.selectPage(pageReqVO); } + @Override + public List getUnitQuantityTree() { + // 1. 查询所有量纲 + List quantities = unitQuantityMapper.selectList(); + + // 2. 查询所有关联关系 + List relations = quantityUnitRelationMapper.selectList(); + + // 3. 查询所有单位 + List units = untInfoMapper.selectList(); + Map unitMap = units.stream() + .collect(Collectors.toMap(UntInfoDO::getId, u -> u)); + + // 4. 组装树形结构 + return quantities.stream().map(quantity -> { + UnitQuantityTreeRespVO treeVO = new UnitQuantityTreeRespVO(); + treeVO.setId(quantity.getId()); + treeVO.setName(quantity.getName()); + treeVO.setSymbol(quantity.getSymbol()); + treeVO.setDsp(quantity.getDsp()); + + // 获取该量纲下的所有单位 + List unitItems = relations.stream() + .filter(r -> r.getUntQtyId().equals(quantity.getId())) + .map(r -> { + UnitQuantityTreeRespVO.UnitItemVO unitItem = new UnitQuantityTreeRespVO.UnitItemVO(); + unitItem.setRelationId(r.getId()); + unitItem.setId(r.getUntId()); + unitItem.setIsBse(r.getIsBse()); + + // 填充单位信息 + UntInfoDO unit = unitMap.get(r.getUntId()); + if (unit != null) { + unitItem.setName(unit.getName()); + unitItem.setSmb(unit.getSmb()); + } + + return unitItem; + }) + .collect(Collectors.toList()); + + treeVO.setUnits(unitItems); + return treeVO; + }).collect(Collectors.toList()); + } + } \ No newline at end of file diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtil.java b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtil.java new file mode 100644 index 0000000..23f0643 --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/main/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtil.java @@ -0,0 +1,407 @@ +package com.zt.plat.module.unitmanagement.util; + +import com.zt.plat.module.unitmanagement.dal.dataobject.UnitConversion.UnitConversionDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.QuantityUnitRelation.QuantityUnitRelationDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.UntInfo.UntInfoDO; +import lombok.extern.slf4j.Slf4j; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.unitmanagement.enums.UnitErrorCodeConstants.*; + +/** + * 单位转换工具类 + * + *

支持三种转换策略: + *

    + *
  • 直接转换:使用预定义的转换规则
  • + *
  • 基准单位转换:通过基准单位进行中转
  • + *
  • 公式转换:使用自定义转换公式
  • + *
+ * + * @author 系统 + */ +@Slf4j +public class UnitConversionUtil { + + /** + * 转换策略枚举 + */ + public enum ConversionStrategy { + /** 直接转换 */ + DIRECT, + /** 基准单位转换 */ + VIA_BASE_UNIT, + /** 公式转换 */ + FORMULA, + /** 无法转换 */ + NONE + } + + /** + * 转换结果 + */ + public static class ConversionResult { + private final BigDecimal value; + private final BigDecimal factor; + private final ConversionStrategy strategy; + private final String formula; + + public ConversionResult(BigDecimal value, BigDecimal factor, ConversionStrategy strategy, String formula) { + this.value = value; + this.factor = factor; + this.strategy = strategy; + this.formula = formula; + } + + public BigDecimal getValue() { + return value; + } + + public BigDecimal getFactor() { + return factor; + } + + public ConversionStrategy getStrategy() { + return strategy; + } + + public String getFormula() { + return formula; + } + } + + /** + * 单位转换 + * + * @param value 待转换的值 + * @param srcUntId 源单位ID + * @param tgtUntId 目标单位ID + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param precision 精度(小数位数) + * @return 转换结果 + */ + public static ConversionResult convert( + BigDecimal value, + Long srcUntId, + Long tgtUntId, + List conversions, + List relations, + int precision) { + + // 1. 如果源单位和目标单位相同,直接返回 + if (srcUntId.equals(tgtUntId)) { + return new ConversionResult(value, BigDecimal.ONE, ConversionStrategy.DIRECT, "相同单位,无需转换"); + } + + // 2. 尝试直接转换 + ConversionResult directResult = tryDirectConversion(value, srcUntId, tgtUntId, conversions, precision); + if (directResult != null) { + return directResult; + } + + // 3. 尝试通过基准单位转换 + ConversionResult viaBaseResult = tryViaBaseUnitConversion(value, srcUntId, tgtUntId, conversions, relations, precision); + if (viaBaseResult != null) { + return viaBaseResult; + } + + // 4. 无法转换 - 抛出业务异常 + log.warn("无法找到从单位 {} 到单位 {} 的转换路径", srcUntId, tgtUntId); + throw exception(UNIT_CONVERSION_PATH_NOT_FOUND, + "ID:" + srcUntId, "ID:" + tgtUntId); + } + + /** + * 尝试直接转换(支持自动反向推导) + */ + private static ConversionResult tryDirectConversion( + BigDecimal value, + Long srcUntId, + Long tgtUntId, + List conversions, + int precision) { + + // 1. 查找正向转换规则:源 → 目标 + UnitConversionDO forwardConversion = conversions.stream() + .filter(c -> c.getSrcUntId().equals(srcUntId) && c.getTgtUntId().equals(tgtUntId)) + .findFirst() + .orElse(null); + + if (forwardConversion != null) { + BigDecimal factor = forwardConversion.getFctr(); + BigDecimal result = value.multiply(factor).setScale(precision, RoundingMode.HALF_UP); + String formula = forwardConversion.getFmu() != null ? forwardConversion.getFmu() : + String.format("value * %s", factor); + + log.debug("直接转换(正向): {} (单位{}) * {} = {} (单位{})", value, srcUntId, factor, result, tgtUntId); + return new ConversionResult(result, factor, ConversionStrategy.DIRECT, formula); + } + + // 2. 查找反向转换规则:目标 → 源,然后取倒数 + UnitConversionDO reverseConversion = conversions.stream() + .filter(c -> c.getSrcUntId().equals(tgtUntId) && c.getTgtUntId().equals(srcUntId)) + .findFirst() + .orElse(null); + + if (reverseConversion != null) { + // 反向系数:如果 目标→源 的系数是 f,则 源→目标 的系数是 1/f + BigDecimal reverseFactor = reverseConversion.getFctr(); + BigDecimal factor = BigDecimal.ONE.divide(reverseFactor, Math.max(precision + 10, 20), RoundingMode.HALF_UP); + BigDecimal result = value.multiply(factor).setScale(precision, RoundingMode.HALF_UP); + String formula = String.format("value / %s (反向推导)", reverseFactor); + + log.debug("直接转换(反向推导): {} (单位{}) / {} = {} (单位{})", value, srcUntId, reverseFactor, result, tgtUntId); + return new ConversionResult(result, factor, ConversionStrategy.DIRECT, formula); + } + + return null; + } + + /** + * 尝试通过基准单位转换 + */ + private static ConversionResult tryViaBaseUnitConversion( + BigDecimal value, + Long srcUntId, + Long tgtUntId, + List conversions, + List relations, + int precision) { + + // 1. 找到源单位和目标单位的量纲 + QuantityUnitRelationDO srcRelation = relations.stream() + .filter(r -> r.getUntId().equals(srcUntId)) + .findFirst() + .orElse(null); + + QuantityUnitRelationDO tgtRelation = relations.stream() + .filter(r -> r.getUntId().equals(tgtUntId)) + .findFirst() + .orElse(null); + + if (srcRelation == null || tgtRelation == null) { + log.warn("找不到单位 {} 或 {} 的量纲关联关系", srcUntId, tgtUntId); + return null; + } + + // 2. 检查是否属于同一量纲 + if (!srcRelation.getUntQtyId().equals(tgtRelation.getUntQtyId())) { + log.warn("单位 {} 和 {} 不属于同一量纲", srcUntId, tgtUntId); + return null; + } + + // 3. 找到基准单位 + Long quantityId = srcRelation.getUntQtyId(); + QuantityUnitRelationDO baseRelation = relations.stream() + .filter(r -> r.getUntQtyId().equals(quantityId) && r.getIsBse() == 1) + .findFirst() + .orElse(null); + + if (baseRelation == null) { + log.warn("量纲 {} 没有设置基准单位", quantityId); + return null; + } + + Long baseUnitId = baseRelation.getUntId(); + + // 4. 源单位 → 基准单位 + BigDecimal toBaseValue; + BigDecimal toBaseFactor; + if (srcUntId.equals(baseUnitId)) { + toBaseValue = value; + toBaseFactor = BigDecimal.ONE; + } else { + ConversionResult toBaseResult = tryDirectConversion(value, srcUntId, baseUnitId, conversions, precision); + if (toBaseResult == null) { + log.warn("无法从源单位 {} 转换到基准单位 {}", srcUntId, baseUnitId); + return null; + } + toBaseValue = toBaseResult.getValue(); + toBaseFactor = toBaseResult.getFactor(); + } + + // 5. 基准单位 → 目标单位 + BigDecimal finalValue; + BigDecimal fromBaseFactor; + if (tgtUntId.equals(baseUnitId)) { + finalValue = toBaseValue; + fromBaseFactor = BigDecimal.ONE; + } else { + ConversionResult fromBaseResult = tryDirectConversion(toBaseValue, baseUnitId, tgtUntId, conversions, precision); + if (fromBaseResult == null) { + log.warn("无法从基准单位 {} 转换到目标单位 {}", baseUnitId, tgtUntId); + return null; + } + finalValue = fromBaseResult.getValue(); + fromBaseFactor = fromBaseResult.getFactor(); + } + + // 6. 计算总转换因子 + BigDecimal totalFactor = toBaseFactor.multiply(fromBaseFactor); + String formula = String.format("通过基准单位(ID:%d): value * %s * %s", baseUnitId, toBaseFactor, fromBaseFactor); + + log.debug("基准单位转换: {} (单位{}) → {} (基准{}) → {} (单位{})", + value, srcUntId, toBaseValue, baseUnitId, finalValue, tgtUntId); + + return new ConversionResult(finalValue, totalFactor, ConversionStrategy.VIA_BASE_UNIT, formula); + } + + /** + * 批量转换 + * + * @param values 待转换的值列表 + * @param srcUntId 源单位ID + * @param tgtUntId 目标单位ID + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param precision 精度(小数位数) + * @return 转换结果列表 + */ + public static List batchConvert( + List values, + Long srcUntId, + Long tgtUntId, + List conversions, + List relations, + int precision) { + + return values.stream() + .map(value -> convert(value, srcUntId, tgtUntId, conversions, relations, precision)) + .collect(Collectors.toList()); + } + + /** + * 按单位符号转换(推荐使用) + * + * @param value 待转换的值 + * @param srcUnitSymbol 源单位符号(如: "kg", "m", "km") + * @param tgtUnitSymbol 目标单位符号 + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param units 单位信息列表 + * @param precision 精度(小数位数) + * @return 转换结果 + */ + public static ConversionResult convertBySymbol( + BigDecimal value, + String srcUnitSymbol, + String tgtUnitSymbol, + List conversions, + List relations, + List units, + int precision) { + + // 1. 根据符号查找单位 + UntInfoDO srcUnit = units.stream() + .filter(u -> u.getSmb() != null && u.getSmb().equals(srcUnitSymbol)) + .findFirst() + .orElseThrow(() -> exception(UNIT_NOT_FOUND, "符号:" + srcUnitSymbol)); + + UntInfoDO tgtUnit = units.stream() + .filter(u -> u.getSmb() != null && u.getSmb().equals(tgtUnitSymbol)) + .findFirst() + .orElseThrow(() -> exception(UNIT_NOT_FOUND, "符号:" + tgtUnitSymbol)); + + log.debug("按符号转换: {} ({}) → {} ({})", srcUnitSymbol, srcUnit.getId(), tgtUnitSymbol, tgtUnit.getId()); + + // 2. 调用原有方法 + return convert(value, srcUnit.getId(), tgtUnit.getId(), conversions, relations, precision); + } + + /** + * 按单位名称转换 + * + * @param value 待转换的值 + * @param srcUnitName 源单位名称(如: "千克", "米", "千米") + * @param tgtUnitName 目标单位名称 + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param units 单位信息列表 + * @param precision 精度(小数位数) + * @return 转换结果 + */ + public static ConversionResult convertByName( + BigDecimal value, + String srcUnitName, + String tgtUnitName, + List conversions, + List relations, + List units, + int precision) { + + // 1. 根据名称查找单位 + UntInfoDO srcUnit = units.stream() + .filter(u -> u.getName() != null && u.getName().equals(srcUnitName)) + .findFirst() + .orElseThrow(() -> exception(UNIT_NOT_FOUND, "名称:" + srcUnitName)); + + UntInfoDO tgtUnit = units.stream() + .filter(u -> u.getName() != null && u.getName().equals(tgtUnitName)) + .findFirst() + .orElseThrow(() -> exception(UNIT_NOT_FOUND, "名称:" + tgtUnitName)); + + log.debug("按名称转换: {} ({}) → {} ({})", srcUnitName, srcUnit.getId(), tgtUnitName, tgtUnit.getId()); + + // 2. 调用原有方法 + return convert(value, srcUnit.getId(), tgtUnit.getId(), conversions, relations, precision); + } + + /** + * 按单位符号批量转换 + * + * @param values 待转换的值列表 + * @param srcUnitSymbol 源单位符号 + * @param tgtUnitSymbol 目标单位符号 + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param units 单位信息列表 + * @param precision 精度(小数位数) + * @return 转换结果列表 + */ + public static List batchConvertBySymbol( + List values, + String srcUnitSymbol, + String tgtUnitSymbol, + List conversions, + List relations, + List units, + int precision) { + + return values.stream() + .map(value -> convertBySymbol(value, srcUnitSymbol, tgtUnitSymbol, conversions, relations, units, precision)) + .collect(Collectors.toList()); + } + + /** + * 按单位名称批量转换 + * + * @param values 待转换的值列表 + * @param srcUnitName 源单位名称 + * @param tgtUnitName 目标单位名称 + * @param conversions 转换规则列表 + * @param relations 量纲-单位关联关系列表 + * @param units 单位信息列表 + * @param precision 精度(小数位数) + * @return 转换结果列表 + */ + public static List batchConvertByName( + List values, + String srcUnitName, + String tgtUnitName, + List conversions, + List relations, + List units, + int precision) { + + return values.stream() + .map(value -> convertByName(value, srcUnitName, tgtUnitName, conversions, relations, units, precision)) + .collect(Collectors.toList()); + } +} diff --git a/zt-module-unit-management/zt-module-unit-management-server/src/test/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtilTest.java b/zt-module-unit-management/zt-module-unit-management-server/src/test/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtilTest.java new file mode 100644 index 0000000..732f50a --- /dev/null +++ b/zt-module-unit-management/zt-module-unit-management-server/src/test/java/com/zt/plat/module/unitmanagement/util/UnitConversionUtilTest.java @@ -0,0 +1,264 @@ +package com.zt.plat.module.unitmanagement.util; + +import com.zt.plat.module.unitmanagement.dal.dataobject.UnitConversion.UnitConversionDO; +import com.zt.plat.module.unitmanagement.dal.dataobject.QuantityUnitRelation.QuantityUnitRelationDO; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * UnitConversionUtil 工具类测试 + * + * 测试内容: + * 1. 相同单位转换 + * 2. 直接转换策略 + * 3. 基准单位转换策略 + * 4. 精度控制 + * 5. 批量转换 + * 6. 错误场景 + */ +class UnitConversionUtilTest { + + /** + * 准备测试数据 + */ + private TestData prepareTestData() { + TestData data = new TestData(); + + // 量纲ID + Long lengthQuantityId = 1L; + Long weightQuantityId = 2L; + + // 单位ID + Long meterId = 101L; // 米(基准) + Long kilometerId = 102L; // 千米 + Long centimeterId = 103L; // 厘米 + Long millimeterId = 104L; // 毫米 + Long kilogramId = 201L; // 千克(基准) + Long tonId = 202L; // 吨 + + // 构建量纲-单位关联关系 + data.relations = new ArrayList<>(); + data.relations.add(createRelation(1L, lengthQuantityId, meterId, 1)); // 米是基准 + data.relations.add(createRelation(2L, lengthQuantityId, kilometerId, 0)); + data.relations.add(createRelation(3L, lengthQuantityId, centimeterId, 0)); + data.relations.add(createRelation(4L, lengthQuantityId, millimeterId, 0)); + data.relations.add(createRelation(5L, weightQuantityId, kilogramId, 1)); // 千克是基准 + data.relations.add(createRelation(6L, weightQuantityId, tonId, 0)); + + // 构建转换规则(只配置到基准单位的转换) + data.conversions = new ArrayList<>(); + + // 千米 <-> 米 + data.conversions.add(createConversion(1L, kilometerId, meterId, new BigDecimal("1000"), "1km = 1000m")); + data.conversions.add(createConversion(2L, meterId, kilometerId, new BigDecimal("0.001"), "1m = 0.001km")); + + // 厘米 <-> 米 + data.conversions.add(createConversion(3L, centimeterId, meterId, new BigDecimal("0.01"), "1cm = 0.01m")); + data.conversions.add(createConversion(4L, meterId, centimeterId, new BigDecimal("100"), "1m = 100cm")); + + // 毫米 <-> 米 + data.conversions.add(createConversion(5L, millimeterId, meterId, new BigDecimal("0.001"), "1mm = 0.001m")); + data.conversions.add(createConversion(6L, meterId, millimeterId, new BigDecimal("1000"), "1m = 1000mm")); + + // 吨 <-> 千克 + data.conversions.add(createConversion(7L, tonId, kilogramId, new BigDecimal("1000"), "1t = 1000kg")); + data.conversions.add(createConversion(8L, kilogramId, tonId, new BigDecimal("0.001"), "1kg = 0.001t")); + + // 保存单位ID供测试使用 + data.meterId = meterId; + data.kilometerId = kilometerId; + data.centimeterId = centimeterId; + data.millimeterId = millimeterId; + data.kilogramId = kilogramId; + data.tonId = tonId; + + return data; + } + + private QuantityUnitRelationDO createRelation(Long id, Long quantityId, Long unitId, Integer isBase) { + QuantityUnitRelationDO relation = new QuantityUnitRelationDO(); + relation.setId(id); + relation.setUntQtyId(quantityId); + relation.setUntId(unitId); + relation.setIsBse(isBase); + return relation; + } + + private UnitConversionDO createConversion(Long id, Long srcId, Long tgtId, BigDecimal factor, String formula) { + UnitConversionDO conversion = new UnitConversionDO(); + conversion.setId(id); + conversion.setSrcUntId(srcId); + conversion.setTgtUntId(tgtId); + conversion.setFctr(factor); + conversion.setFmu(formula); + return conversion; + } + + @Test + @DisplayName("测试1: 相同单位转换") + void testSameUnitConversion() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("100"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.meterId, data.meterId, + data.conversions, data.relations, 6 + ); + + assertEquals(value, result.getValue(), "相同单位转换,值应该不变"); + assertEquals(BigDecimal.ONE, result.getFactor(), "相同单位转换,因子应该为1"); + assertEquals(UnitConversionUtil.ConversionStrategy.DIRECT, result.getStrategy(), "相同单位应使用DIRECT策略"); + } + + @Test + @DisplayName("测试2: 直接转换策略 - 千米到米") + void testDirectConversion_KmToM() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("5.5"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.kilometerId, data.meterId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("5500.000000"), result.getValue(), "5.5km应该等于5500m"); + assertEquals(new BigDecimal("1000"), result.getFactor(), "千米到米的因子应该是1000"); + assertEquals(UnitConversionUtil.ConversionStrategy.DIRECT, result.getStrategy(), "应使用DIRECT策略"); + } + + @Test + @DisplayName("测试3: 直接转换策略 - 米到千米") + void testDirectConversion_MToKm() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("5000"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.meterId, data.kilometerId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("5.000000"), result.getValue(), "5000m应该等于5km"); + assertEquals(new BigDecimal("0.001"), result.getFactor(), "米到千米的因子应该是0.001"); + assertEquals(UnitConversionUtil.ConversionStrategy.DIRECT, result.getStrategy(), "应使用DIRECT策略"); + } + + @Test + @DisplayName("测试4: 基准单位转换策略 - 厘米到千米") + void testViaBaseUnitConversion_CmToKm() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("100000"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.centimeterId, data.kilometerId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("1.000000"), result.getValue(), "100000cm应该等于1km"); + assertEquals(UnitConversionUtil.ConversionStrategy.VIA_BASE_UNIT, result.getStrategy(), + "厘米到千米应使用VIA_BASE_UNIT策略"); + assertTrue(result.getFormula().contains("基准单位"), "公式应该提到基准单位"); + } + + @Test + @DisplayName("测试5: 基准单位转换策略 - 毫米到千米") + void testViaBaseUnitConversion_MmToKm() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("1000000"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.millimeterId, data.kilometerId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("1.000000"), result.getValue(), "1000000mm应该等于1km"); + assertEquals(UnitConversionUtil.ConversionStrategy.VIA_BASE_UNIT, result.getStrategy(), + "毫米到千米应使用VIA_BASE_UNIT策略"); + } + + @Test + @DisplayName("测试6: 精度控制 - 2位小数") + void testPrecisionControl_2Digits() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("1.23456789"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.kilometerId, data.meterId, + data.conversions, data.relations, 2 + ); + + assertEquals(new BigDecimal("1234.57"), result.getValue(), "精度应该控制在2位小数"); + } + + @Test + @DisplayName("测试7: 精度控制 - 6位小数") + void testPrecisionControl_6Digits() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("1.23456789"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.kilometerId, data.meterId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("1234.567890"), result.getValue(), "精度应该控制在6位小数"); + } + + @Test + @DisplayName("测试8: 批量转换") + void testBatchConversion() { + TestData data = prepareTestData(); + + List values = new ArrayList<>(); + values.add(new BigDecimal("1")); + values.add(new BigDecimal("2")); + values.add(new BigDecimal("3")); + + List results = UnitConversionUtil.batchConvert( + values, data.kilometerId, data.meterId, + data.conversions, data.relations, 6 + ); + + assertEquals(3, results.size(), "应该返回3个转换结果"); + assertEquals(new BigDecimal("1000.000000"), results.get(0).getValue(), "1km = 1000m"); + assertEquals(new BigDecimal("2000.000000"), results.get(1).getValue(), "2km = 2000m"); + assertEquals(new BigDecimal("3000.000000"), results.get(2).getValue(), "3km = 3000m"); + } + + + + @Test + @DisplayName("测试10: 重量单位转换 - 千克到吨") + void testWeightConversion_KgToTon() { + TestData data = prepareTestData(); + + BigDecimal value = new BigDecimal("1000"); + UnitConversionUtil.ConversionResult result = UnitConversionUtil.convert( + value, data.kilogramId, data.tonId, + data.conversions, data.relations, 6 + ); + + assertEquals(new BigDecimal("1.000000"), result.getValue(), "1000kg应该等于1t"); + assertEquals(new BigDecimal("0.001"), result.getFactor(), "千克到吨的因子应该是0.001"); + assertEquals(UnitConversionUtil.ConversionStrategy.DIRECT, result.getStrategy()); + } + + /** + * 测试数据容器 + */ + static class TestData { + List conversions; + List relations; + Long meterId; + Long kilometerId; + Long centimeterId; + Long millimeterId; + Long kilogramId; + Long tonId; + } +}