diff --git a/.gitignore b/.gitignore index 0a488b1..f1f34d5 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ log/ # Claude .claude/commands/openspec +.claude/skills/codegen .spec-workflow .bmad-core .claude/commands/BMad diff --git a/erp-turbo-admin/src/main/java/com/xunhong/erp/turbo/admin/controller/OcrController.java b/erp-turbo-admin/src/main/java/com/xunhong/erp/turbo/admin/controller/OcrController.java index 58792a2..e0f3d73 100644 --- a/erp-turbo-admin/src/main/java/com/xunhong/erp/turbo/admin/controller/OcrController.java +++ b/erp-turbo-admin/src/main/java/com/xunhong/erp/turbo/admin/controller/OcrController.java @@ -1,11 +1,11 @@ package com.xunhong.erp.turbo.admin.controller; import cn.dev33.satoken.annotation.SaCheckLogin; -import cn.hutool.core.date.LocalDateTimeUtil; import com.alibaba.cola.dto.SingleResponse; import com.xunhong.erp.turbo.api.facade.api.WxMaServiceI; import com.xunhong.erp.turbo.api.facade.dto.vo.WxMaOcrBankCardVO; import com.xunhong.erp.turbo.api.facade.dto.vo.WxMaOcrIdCardVO; +import com.xunhong.erp.turbo.base.utils.FileUploadUtil; import com.xunhong.erp.turbo.file.FileService; import com.xunhong.erp.turbo.file.config.OssProperties; import io.swagger.v3.oas.annotations.Operation; @@ -40,7 +40,8 @@ public class OcrController { @PostMapping(value = "ocrIdCard", consumes = "multipart/form-data") @Operation(summary = "OCR识别身份证", method = "POST", hidden = true) public SingleResponse ocrIdCard(@RequestParam @Schema(title = "图片文件", requiredMode = Schema.RequiredMode.REQUIRED, type = "file") MultipartFile file) throws IOException { - String objectName = "uploads/" + LocalDateTimeUtil.format(LocalDateTimeUtil.now(), "yyMM/dd") + "/" + file.getOriginalFilename(); + // 使用FileUploadUtil安全处理文件名 + String objectName = FileUploadUtil.generateObjectName(file); fileService.upload(objectName, file.getInputStream()); String ocrUrl = properties.getDomain() + objectName; @@ -51,7 +52,8 @@ public class OcrController { @PostMapping(value = "ocrBankCard", consumes = "multipart/form-data") @Operation(summary = "OCR识别银行卡", method = "POST", hidden = true) public SingleResponse ocrBankCard(@RequestParam @Schema(title = "图片文件", requiredMode = Schema.RequiredMode.REQUIRED, type = "file") MultipartFile file) throws IOException { - String objectName = "uploads/" + LocalDateTimeUtil.format(LocalDateTimeUtil.now(), "yyMM/dd") + "/" + file.getOriginalFilename(); + // 使用FileUploadUtil安全处理文件名 + String objectName = FileUploadUtil.generateObjectName(file); fileService.upload(objectName, file.getInputStream()); String ocrUrl = properties.getDomain() + objectName; diff --git a/erp-turbo-auth/src/main/java/com/xunhong/erp/turbo/auth/controller/UserController.java b/erp-turbo-auth/src/main/java/com/xunhong/erp/turbo/auth/controller/UserController.java index 528dc10..b60395f 100644 --- a/erp-turbo-auth/src/main/java/com/xunhong/erp/turbo/auth/controller/UserController.java +++ b/erp-turbo-auth/src/main/java/com/xunhong/erp/turbo/auth/controller/UserController.java @@ -2,7 +2,6 @@ package com.xunhong.erp.turbo.auth.controller; import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.tree.Tree; import com.alibaba.cola.dto.MultiResponse; import com.alibaba.cola.dto.Response; @@ -30,6 +29,7 @@ import com.xunhong.erp.turbo.api.user.dto.vo.EmployeeVO; import com.xunhong.erp.turbo.api.user.dto.vo.UserAuthVO; import com.xunhong.erp.turbo.api.user.dto.vo.UserVO; import com.xunhong.erp.turbo.base.dto.UserSession; +import com.xunhong.erp.turbo.base.utils.FileUploadUtil; import com.xunhong.erp.turbo.file.FileService; import com.xunhong.erp.turbo.file.config.OssProperties; import io.swagger.v3.oas.annotations.Operation; @@ -191,7 +191,14 @@ public class UserController { @PostMapping(value = "upload", consumes = "multipart/form-data") @Operation(summary = "上传图片", method = "POST", hidden = true) public SingleResponse upload(@RequestParam @Schema(title = "图片文件", requiredMode = Schema.RequiredMode.REQUIRED, type = "file") MultipartFile file) throws IOException { - String objectName = "uploads/" + LocalDateTimeUtil.format(LocalDateTimeUtil.now(), "yyMM/dd") + "/" + file.getOriginalFilename(); + // 使用FileUploadUtil安全处理文件名 + String objectName = FileUploadUtil.generateObjectName(file); + + // 记录上传信息 + System.out.println("上传文件: " + file.getOriginalFilename() + + ", Content-Type: " + file.getContentType() + + ", 生成对象名: " + objectName); + fileService.upload(objectName, file.getInputStream()); return SingleResponse.of(properties.getDomain() + objectName); } diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/domain/entity/PurchaseOrder.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/domain/entity/PurchaseOrder.java index db74b80..09d2471 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/domain/entity/PurchaseOrder.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/domain/entity/PurchaseOrder.java @@ -9,7 +9,6 @@ import com.xunhong.erp.turbo.api.biz.dto.enums.PurchaseOrderStateEnum; import lombok.Data; import lombok.EqualsAndHashCode; -import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -51,51 +50,6 @@ public class PurchaseOrder extends DTO { */ private PurchaseOrderPricingMethodEnum pricingMethod; - /** - * 销售金额 - */ - private BigDecimal saleAmount; - - /** - * 包装费 - */ - private BigDecimal packageFee; - - /** - * 平均单价(元/斤) - */ - private BigDecimal avgUnitPrice; - - /** - * 是否返点 - */ - private Boolean rebate; - - /** - * 毛重(斤) - */ - private BigDecimal grossWeight; - - /** - * 净重(斤) - */ - private BigDecimal netWeight; - - /** - * 成本合计 - */ - private BigDecimal totalCost; - - /** - * 运费 - */ - private BigDecimal freightCharge; - - /** - * 瓜农数量 - */ - private Integer supplierCount; - /** * 采购订单状态: 0_草稿;1_审核中;2_已完成;3_已驳回;4_已关闭; */ diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/convert/PurchaseOrderConvert.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/convert/PurchaseOrderConvert.java index ed21844..b5292ff 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/convert/PurchaseOrderConvert.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/convert/PurchaseOrderConvert.java @@ -27,18 +27,9 @@ public interface PurchaseOrderConvert { @Mapping(target = "orderCostItemDOList", ignore = true) @Mapping(target = "auditState", ignore = true) @Mapping(target = "orderPackageDOList", ignore = true) - @Mapping(target = "totalCost", ignore = true) - @Mapping(target = "supplierCount", ignore = true) - @Mapping(target = "saleAmount", ignore = true) - @Mapping(target = "rebate", ignore = true) @Mapping(target = "pricingMethod", ignore = true) - @Mapping(target = "packageFee", ignore = true) @Mapping(target = "orderRebateDO", ignore = true) @Mapping(target = "orderCompanyDO", ignore = true) - @Mapping(target = "netWeight", ignore = true) - @Mapping(target = "grossWeight", ignore = true) - @Mapping(target = "freightCharge", ignore = true) - @Mapping(target = "avgUnitPrice", ignore = true) @Mapping(target = "orderDealerDO", ignore = true) @Mapping(target = "orderCostDOList", ignore = true) @Mapping(target = "orderSn", ignore = true) @@ -55,18 +46,9 @@ public interface PurchaseOrderConvert { @Mapping(target = "orderCostItemDOList", ignore = true) @Mapping(target = "auditState", ignore = true) @Mapping(target = "orderPackageDOList", ignore = true) - @Mapping(target = "totalCost", ignore = true) - @Mapping(target = "supplierCount", ignore = true) - @Mapping(target = "saleAmount", ignore = true) - @Mapping(target = "rebate", ignore = true) @Mapping(target = "pricingMethod", ignore = true) - @Mapping(target = "packageFee", ignore = true) @Mapping(target = "orderRebateDO", ignore = true) @Mapping(target = "orderCompanyDO", ignore = true) - @Mapping(target = "netWeight", ignore = true) - @Mapping(target = "grossWeight", ignore = true) - @Mapping(target = "freightCharge", ignore = true) - @Mapping(target = "avgUnitPrice", ignore = true) @Mapping(target = "orderDealerDO", ignore = true) @Mapping(target = "orderCostDOList", ignore = true) @Mapping(target = "orderVehicleDO", ignore = true) @@ -93,14 +75,9 @@ public interface PurchaseOrderConvert { @Mapping(target = "originPrincipal", source = "createdByName") @Mapping(target = "version", ignore = true) @Mapping(target = "updatedAt", ignore = true) - @Mapping(target = "totalCost", ignore = true) - @Mapping(target = "supplierCount", ignore = true) @Mapping(target = "state", ignore = true) - @Mapping(target = "saleAmount", ignore = true) @Mapping(target = "remark", ignore = true) - @Mapping(target = "rebate", ignore = true) @Mapping(target = "pricingMethod", ignore = true) - @Mapping(target = "packageFee", ignore = true) @Mapping(target = "orderVehicleDO", ignore = true) @Mapping(target = "orderSupplierDOList", ignore = true) @Mapping(target = "orderSn", ignore = true) @@ -108,12 +85,8 @@ public interface PurchaseOrderConvert { @Mapping(target = "orderDealerDO", ignore = true) @Mapping(target = "orderCostDOList", ignore = true) @Mapping(target = "orderCompanyDO", ignore = true) - @Mapping(target = "netWeight", ignore = true) @Mapping(target = "isDelete", ignore = true) - @Mapping(target = "grossWeight", ignore = true) - @Mapping(target = "freightCharge", ignore = true) @Mapping(target = "createdAt", ignore = true) - @Mapping(target = "avgUnitPrice", ignore = true) PurchaseOrderDO toPurchaseOrderDO(PurchaseOrderStep1Cmd purchaseOrderStep1Cmd); } diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderPackageDO.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderPackageDO.java index cc5570a..f335c84 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderPackageDO.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderPackageDO.java @@ -66,7 +66,7 @@ public class OrderPackageDO extends BaseDO { * 箱子规格id */ @TableField(value = "box_spec_id") - private String boxSpecId; + private Long boxSpecId; /** * 箱子规格名称 diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderSupplierDO.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderSupplierDO.java index a0254e3..2282c53 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderSupplierDO.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/OrderSupplierDO.java @@ -5,6 +5,9 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.xunhong.erp.turbo.api.biz.dto.common.SupplierPackageUsage; +import com.xunhong.erp.turbo.api.biz.dto.common.UploadFileItem; +import com.xunhong.erp.turbo.api.biz.dto.enums.PurchaseOrderPricingMethodEnum; import com.xunhong.erp.turbo.datasource.domain.entity.BaseDO; import lombok.Data; import lombok.EqualsAndHashCode; @@ -110,12 +113,24 @@ public class OrderSupplierDO extends BaseDO { @TableField(value = "purchase_price") private BigDecimal purchasePrice; + /** + * 箱子类型 + */ + @TableField(value = "package_usage", typeHandler = JacksonTypeHandler.class) + private List packageUsage; + /** * 销售单价(元/斤) */ @TableField(value = "sale_price") private BigDecimal salePrice; + /** + * 报价方式:1_按毛重报价;2_按净重报价; + */ + @TableField(value = "pricing_method") + private PurchaseOrderPricingMethodEnum pricingMethod; + /** * 发票金额 */ @@ -144,7 +159,7 @@ public class OrderSupplierDO extends BaseDO { * 发票 */ @TableField(value = "invoice_img", typeHandler = JacksonTypeHandler.class) - private List invoiceImg; + private List invoiceImg; /** * 是否上传合同 @@ -156,7 +171,7 @@ public class OrderSupplierDO extends BaseDO { * 合同 */ @TableField(value = "contract_img", typeHandler = JacksonTypeHandler.class) - private List contractImg; + private List contractImg; /** * 产品ID diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/PurchaseOrderDO.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/PurchaseOrderDO.java index c4e705d..245fb94 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/PurchaseOrderDO.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/PurchaseOrderDO.java @@ -11,7 +11,6 @@ import com.xunhong.erp.turbo.datasource.domain.entity.BaseDO; import lombok.Data; import lombok.EqualsAndHashCode; -import java.math.BigDecimal; import java.util.List; /** @@ -58,60 +57,6 @@ public class PurchaseOrderDO extends BaseDO { @TableField(value = "pricing_method") private PurchaseOrderPricingMethodEnum pricingMethod; - /** - * 销售金额 - */ - @TableField(value = "sale_amount") - private BigDecimal saleAmount; - - /** - * 包装费 - */ - @TableField(value = "package_fee") - private BigDecimal packageFee; - - /** - * 平均单价(元/斤) - */ - @TableField(value = "avg_unit_price") - private BigDecimal avgUnitPrice; - - /** - * 是否返点 - */ - @TableField(value = "rebate") - private Boolean rebate; - - /** - * 毛重(斤) - */ - @TableField(value = "gross_weight") - private BigDecimal grossWeight; - - /** - * 净重(斤) - */ - @TableField(value = "net_weight") - private BigDecimal netWeight; - - /** - * 成本合计 - */ - @TableField(value = "total_cost") - private BigDecimal totalCost; - - /** - * 运费 - */ - @TableField(value = "freight_charge") - private BigDecimal freightCharge; - - /** - * 瓜农数量 - */ - @TableField(value = "supplier_count") - private Integer supplierCount; - /** * 采购订单状态: 0_草稿;1_审核中;2_已完成;3_已驳回;4_已关闭; */ diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/ShipOrderPackageDO.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/ShipOrderPackageDO.java index a2cb82c..f82e9ac 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/ShipOrderPackageDO.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/entity/ShipOrderPackageDO.java @@ -34,7 +34,7 @@ public class ShipOrderPackageDO extends BaseDO { * 箱型规格id */ @TableField(value = "box_spec_id") - private String boxSpecId; + private Long boxSpecId; /** * 箱型规格名称 diff --git a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/gateway/PurchaseOrderGatewayImpl.java b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/gateway/PurchaseOrderGatewayImpl.java index 4d95f17..f1e4637 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/gateway/PurchaseOrderGatewayImpl.java +++ b/erp-turbo-business/erp-turbo-biz/src/main/java/com/xunhong/erp/turbo/biz/infrastructure/gateway/PurchaseOrderGatewayImpl.java @@ -8,7 +8,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xunhong.erp.turbo.api.biz.dto.cmd.*; import com.xunhong.erp.turbo.api.biz.dto.common.*; -import com.xunhong.erp.turbo.api.biz.dto.enums.*; +import com.xunhong.erp.turbo.api.biz.dto.enums.OrderAuditStateEnum; +import com.xunhong.erp.turbo.api.biz.dto.enums.OrderAuditTypeEnum; +import com.xunhong.erp.turbo.api.biz.dto.enums.PurchaseOrderAuditStateEnum; +import com.xunhong.erp.turbo.api.biz.dto.enums.PurchaseOrderStateEnum; import com.xunhong.erp.turbo.api.biz.dto.qry.*; import com.xunhong.erp.turbo.api.biz.exception.BizErrorCode; import com.xunhong.erp.turbo.api.biz.exception.BizException; @@ -21,8 +24,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -503,230 +504,6 @@ public class PurchaseOrderGatewayImpl implements PurchaseOrderGateway { throw new BizException(BizErrorCode.B_BIZ_ORDER_AUDIT_NOT_FOUND); } - // 自动生成发货单 purchaseOrderDO 生成 shipOrder,orderPackage 变成 shipOrderPackage(按照报价价格分组插入),orderSupplier 变成shipOrderItem(按照规格分组插入) - // 1. 创建发货单 - ShipOrderDO shipOrderDO = new ShipOrderDO(); - shipOrderDO.setPurchaseOrderId(purchaseOrderDO.getOrderId()); - shipOrderDO.setOrderSn("FH" + generateShipOrderSn()); // 生成发货单编号 - - // 从车辆信息中获取相关信息 - OrderVehicleDO orderVehicleDO = orderVehicleMapper.selectOne( - Wrappers.lambdaQuery(OrderVehicleDO.class) - .eq(OrderVehicleDO::getOrderId, purchaseOrderDO.getOrderId()) - ); - - if (orderVehicleDO != null) { - shipOrderDO.setLicensePlate(orderVehicleDO.getPlate()); - shipOrderDO.setDriverName(orderVehicleDO.getDriver()); - shipOrderDO.setDriverPhone(orderVehicleDO.getPhone()); - shipOrderDO.setShippingDate(orderVehicleDO.getDeliveryTime()); - shipOrderDO.setShippingAddress(orderVehicleDO.getOrigin()); - shipOrderDO.setReceivingAddress(orderVehicleDO.getDestination()); - shipOrderDO.setFreightDebt(orderVehicleDO.getPrice()); - if (orderVehicleDO.getOpenStrawCurtain()) { - shipOrderDO.setStrawMatDebt(orderVehicleDO.getStrawCurtainPrice()); - } - shipOrderDO.setDealerId(orderVehicleDO.getDealerId()); - shipOrderDO.setDealerName(orderVehicleDO.getDealerName()); - shipOrderDO.setVehicleNo(orderVehicleDO.getVehicleNo()); - } - - // 从公司信息中获取相关信息 - OrderCompanyDO orderCompanyDO = orderCompanyMapper.selectOne( - Wrappers.lambdaQuery(OrderCompanyDO.class) - .eq(OrderCompanyDO::getOrderId, purchaseOrderDO.getOrderId()) - ); - - if (orderCompanyDO != null) { - shipOrderDO.setCompanyId(orderCompanyDO.getCompanyId()); - shipOrderDO.setCompanyName(orderCompanyDO.getFullName()); - } - - // 从经销商信息中获取相关信息 - OrderDealerDO orderDealerDO = orderDealerMapper.selectOne( - Wrappers.lambdaQuery(OrderDealerDO.class) - .eq(OrderDealerDO::getOrderId, purchaseOrderDO.getOrderId()) - ); - - // 设置其他基本信息 - shipOrderDO.setCreatedBy(purchaseOrderFinalApproveCmd.getCreatedBy()); - shipOrderDO.setCreatedByName(purchaseOrderFinalApproveCmd.getCreatedByName()); - - // 4. 从OrderCostDO中提取人工费、商标费等费用信息 - List orderCostDOList = orderCostMapper.selectList( - Wrappers.lambdaQuery(OrderCostDO.class) - .eq(OrderCostDO::getOrderId, purchaseOrderDO.getOrderId()) - ); - - // 计算人工费总额 - BigDecimal totalLaborFee = orderCostDOList.stream() - .filter(cost -> CostItemTypeEnum.ARTIFICIAL_TYPE.getType() == cost.getType().getType()) - .map(cost -> { - if (cost.getPrice() != null && cost.getCount() != null) { - return cost.getPrice().multiply(BigDecimal.valueOf(cost.getCount())); - } - return BigDecimal.ZERO; - }) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderDO.setLaborFee(totalLaborFee); - - // 计算商标费总额 - BigDecimal totalTrademarkFee = orderCostDOList.stream() - .filter(cost -> "商标费".equals(cost.getName())) - .map(cost -> { - if (cost.getPrice() != null && cost.getCount() != null) { - return cost.getPrice().multiply(BigDecimal.valueOf(cost.getCount())); - } - return BigDecimal.ZERO; - }) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderDO.setTrademarkFee(totalTrademarkFee); - - // 计算打码费总额 - BigDecimal totalCodingFee = orderCostDOList.stream() - .filter(cost -> "打码费".equals(cost.getName())) - .map(cost -> { - if (cost.getPrice() != null && cost.getCount() != null) { - return cost.getPrice().multiply(BigDecimal.valueOf(cost.getCount())); - } - return BigDecimal.ZERO; - }) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderDO.setTrademarkFee(totalCodingFee); - - shipOrderDO.setCodingFee(totalTrademarkFee); - - // 2. 处理包材信息,转换为发货单包材信息(按照boxCategoryId和boxProductName分组) - List orderSupplierDOList = orderSupplierMapper.selectList( - Wrappers.lambdaQuery(OrderSupplierDO.class) - .eq(OrderSupplierDO::getOrderId, purchaseOrderDO.getOrderId()) - ); - - // 收集所有的包材信息 - List orderSupplierIdList = orderSupplierDOList.stream() - .map(OrderSupplierDO::getOrderSupplierId) - .toList(); - - List orderPackageDOList = new ArrayList<>(); - if (CollUtil.isNotEmpty(orderSupplierIdList)) { - orderPackageDOList = orderPackageMapper.selectList( - Wrappers.lambdaQuery(OrderPackageDO.class) - .in(OrderPackageDO::getOrderSupplierId, orderSupplierIdList) - ); - } - // 计算纸箱费总额 - BigDecimal totalCartonFee = orderPackageDOList.stream() - .map(p -> { - if (p.getBoxSalePrice() != null && p.getBoxCount() != null) { - return p.getBoxSalePrice().multiply(BigDecimal.valueOf(p.getBoxCount())); - } - return BigDecimal.ZERO; - }) - .reduce(BigDecimal.ZERO, BigDecimal::add); - - shipOrderDO.setCartonFee(totalCartonFee); - - // 插入发货单 - shipOrderMapper.insert(shipOrderDO); - - // 按boxCategoryId和boxProductName分组包材信息 - Map> packageGroupByKey = orderPackageDOList.stream() - .collect(Collectors.groupingBy(p -> p.getBoxSpecId() + "_" + p.getBoxProductName())); - - // 创建发货单包材信息 - for (Map.Entry> entry : packageGroupByKey.entrySet()) { - List packages = entry.getValue(); - - // 合并相同类型的包材信息 - ShipOrderPackageDO shipOrderPackageDO = new ShipOrderPackageDO(); - shipOrderPackageDO.setShipOrderId(shipOrderDO.getShipOrderId()); - - // 使用第一个包材的信息作为基础 - OrderPackageDO firstPackage = packages.get(0); - shipOrderPackageDO.setBoxSpecId(firstPackage.getBoxSpecId()); - shipOrderPackageDO.setBoxSpecName(firstPackage.getBoxSpecName()); - shipOrderPackageDO.setBoxProduct(firstPackage.getBoxProductName()); - - // 使用第一个包材的销售价格作为单价 - shipOrderPackageDO.setUnitPrice(firstPackage.getBoxSalePrice()); - - // 计算总数量 - int totalQuantity = packages.stream() - .mapToInt(OrderPackageDO::getBoxCount) - .sum(); - shipOrderPackageDO.setQuantity(totalQuantity); - - // 计算总金额 - BigDecimal unitPrice = firstPackage.getBoxSalePrice() != null ? firstPackage.getBoxSalePrice() : BigDecimal.ZERO; - BigDecimal totalAmount = unitPrice.multiply(BigDecimal.valueOf(totalQuantity)); - shipOrderPackageDO.setItemAmount(totalAmount); - - // 计算总重量(如果有) - BigDecimal totalWeight = packages.stream() - .map(p -> { - if (p.getBoxProductWeight() != null && p.getBoxCount() != null) { - return p.getBoxProductWeight().multiply(BigDecimal.valueOf(p.getBoxCount())); - } - return BigDecimal.ZERO; - }) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderPackageDO.setTotalWeight(totalWeight); - - shipOrderPackageDO.setSingleWeight(shipOrderPackageDO.getTotalWeight().divide(shipOrderPackageDO.getUnitPrice(), 2, RoundingMode.HALF_UP)); - - shipOrderPackageMapper.insert(shipOrderPackageDO); - } - - // 3. 处理供应商信息,转换为发货单项信息(按照规格分组) - // 按销售单价分组供应商信息 - Map> supplierGroupByPrice = orderSupplierDOList.stream() - .collect(Collectors.groupingBy(OrderSupplierDO::getSalePrice)); - - // 创建发货单项信息 - for (Map.Entry> entry : supplierGroupByPrice.entrySet()) { - List suppliers = entry.getValue(); - - // 合并相同价格的供应商信息 - ShipOrderItemDO shipOrderItemDO = new ShipOrderItemDO(); - shipOrderItemDO.setShipOrderId(shipOrderDO.getShipOrderId()); - - // 计算总毛重 - BigDecimal totalGrossWeight = suppliers.stream() - .map(OrderSupplierDO::getGrossWeight) - .filter(Objects::nonNull) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderItemDO.setGrossWeight(totalGrossWeight); - - // 计算总净重 - BigDecimal totalNetWeight = suppliers.stream() - .map(OrderSupplierDO::getNetWeight) - .filter(Objects::nonNull) - .reduce(BigDecimal.ZERO, BigDecimal::add); - shipOrderItemDO.setNetWeight(totalNetWeight); - - shipOrderItemDO.setBoxWeight(totalGrossWeight.subtract(totalNetWeight)); - - // 计算总箱数 - Integer totalBoxCount = orderPackageDOList.stream() - .filter(p -> suppliers.stream().map(OrderSupplierDO::getOrderSupplierId).toList().contains(p.getOrderSupplierId())) - .map(OrderPackageDO::getBoxCount) - .reduce(0, Integer::sum); - - shipOrderItemDO.setBoxCount(totalBoxCount); - - // 设置单价 - BigDecimal salePrice = entry.getKey(); - shipOrderItemDO.setUnitPrice(salePrice); - - // 计算总金额 - if (salePrice != null) { - BigDecimal totalAmount = salePrice.multiply(totalNetWeight); - shipOrderItemDO.setTotalAmount(totalAmount); - } - - shipOrderItemMapper.insert(shipOrderItemDO); - } - purchaseOrderMapper.updateById(purchaseOrderDO); } @@ -807,6 +584,7 @@ public class PurchaseOrderGatewayImpl implements PurchaseOrderGateway { queryWrapper.last("limit 1"); purchaseOrderDO = purchaseOrderMapper.selectOne(queryWrapper); purchaseOrderDO.setActive(purchaseOrderStep1Cmd.getActive()); + purchaseOrderDO.setState(PurchaseOrderStateEnum.DRAFT); purchaseOrderMapper.updateById(purchaseOrderDO); } @@ -867,6 +645,7 @@ public class PurchaseOrderGatewayImpl implements PurchaseOrderGateway { queryWrapper.last("limit 1"); PurchaseOrderDO purchaseOrderDO = purchaseOrderMapper.selectOne(queryWrapper); purchaseOrderDO.setActive(purchaseOrderStep2Cmd.getActive()); + purchaseOrderDO.setState(PurchaseOrderStateEnum.DRAFT); purchaseOrderMapper.updateById(purchaseOrderDO); // 更新供应商信息(精细化处理) @@ -1019,6 +798,7 @@ public class PurchaseOrderGatewayImpl implements PurchaseOrderGateway { PurchaseOrderDO purchaseOrderDO = purchaseOrderMapper.selectOne(queryWrapper); purchaseOrderDO.setActive(purchaseOrderStep3Cmd.getActive()); purchaseOrderDO.setForeman(purchaseOrderStep3Cmd.getForeman()); + purchaseOrderDO.setState(PurchaseOrderStateEnum.DRAFT); purchaseOrderMapper.updateById(purchaseOrderDO); saveOrderCostItem(orderId, purchaseOrderStep3Cmd.getOrderCostItemList().stream().toList()); diff --git a/erp-turbo-business/erp-turbo-biz/src/main/resources/mapper/OrderSupplierMapper.xml b/erp-turbo-business/erp-turbo-biz/src/main/resources/mapper/OrderSupplierMapper.xml index 7d8350a..afa4569 100644 --- a/erp-turbo-business/erp-turbo-biz/src/main/resources/mapper/OrderSupplierMapper.xml +++ b/erp-turbo-business/erp-turbo-biz/src/main/resources/mapper/OrderSupplierMapper.xml @@ -20,10 +20,13 @@ + + - - + + - - - - - - - - - - diff --git a/erp-turbo-business/erp-turbo-infra/src/main/java/com/xunhong/erp/turbo/infra/infrastructure/gateway/OssGatewayImpl.java b/erp-turbo-business/erp-turbo-infra/src/main/java/com/xunhong/erp/turbo/infra/infrastructure/gateway/OssGatewayImpl.java index e239b2a..fd05d2a 100644 --- a/erp-turbo-business/erp-turbo-infra/src/main/java/com/xunhong/erp/turbo/infra/infrastructure/gateway/OssGatewayImpl.java +++ b/erp-turbo-business/erp-turbo-infra/src/main/java/com/xunhong/erp/turbo/infra/infrastructure/gateway/OssGatewayImpl.java @@ -1,6 +1,7 @@ package com.xunhong.erp.turbo.infra.infrastructure.gateway; import cn.hutool.core.date.LocalDateTimeUtil; +import com.xunhong.erp.turbo.base.utils.FileUploadUtil; import com.xunhong.erp.turbo.file.FileService; import com.xunhong.erp.turbo.file.config.OssProperties; import com.xunhong.erp.turbo.infra.domain.entity.Credentials; @@ -37,7 +38,8 @@ public class OssGatewayImpl implements OssGateway { @Override public String upload(MultipartFile file) { - String objectName = "uploads/" + LocalDateTimeUtil.format(LocalDateTimeUtil.now(), "yyMM/dd") + "/" + file.getOriginalFilename(); + // 使用FileUploadUtil安全处理文件名 + String objectName = FileUploadUtil.generateObjectName(file); try { fileService.upload(objectName, file.getInputStream()); diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/cmd/OrderSupplierUpdateCmd.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/cmd/OrderSupplierUpdateCmd.java index 77f0d5b..0a1b852 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/cmd/OrderSupplierUpdateCmd.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/cmd/OrderSupplierUpdateCmd.java @@ -1,5 +1,6 @@ package com.xunhong.erp.turbo.api.biz.dto.cmd; +import com.xunhong.erp.turbo.api.biz.dto.common.UploadFileItem; import com.xunhong.erp.turbo.base.dto.Command; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -29,7 +30,7 @@ public class OrderSupplierUpdateCmd extends Command { * 发票照片 */ @Schema(title = "发票照片") - private List invoiceImg; + private List invoiceImg; /** * 是否上传合同 @@ -41,7 +42,7 @@ public class OrderSupplierUpdateCmd extends Command { * 合同照片 */ @Schema(title = "合同照片") - private List contractImg; + private List contractImg; /** * 是否已付定金 diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderPackage.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderPackage.java index 8f1a63f..106c0f5 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderPackage.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderPackage.java @@ -62,8 +62,8 @@ public class OrderPackage extends Command { /** * 箱子规格ID */ - @Schema(title = "箱子规格ID", requiredMode = Schema.RequiredMode.REQUIRED) - private String boxSpecId; + @Schema(title = "箱子规格ID", type = "string", requiredMode = Schema.RequiredMode.REQUIRED) + private Long boxSpecId; /** * 箱子规格名称 diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderSupplier.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderSupplier.java index 0ad1efc..6c7606c 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderSupplier.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/OrderSupplier.java @@ -1,5 +1,6 @@ package com.xunhong.erp.turbo.api.biz.dto.common; +import com.xunhong.erp.turbo.api.biz.dto.enums.PurchaseOrderPricingMethodEnum; import com.xunhong.erp.turbo.base.dto.Command; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -106,12 +107,24 @@ public class OrderSupplier extends Command { @Schema(title = "采购单价(元/斤)", requiredMode = Schema.RequiredMode.REQUIRED) private BigDecimal purchasePrice; + /** + * 箱子类型 + */ + @Schema(title = "箱子类型", requiredMode = Schema.RequiredMode.REQUIRED) + private List packageUsage; + /** * 销售单价(元/斤) */ @Schema(title = "销售单价(元/斤)", requiredMode = Schema.RequiredMode.REQUIRED) private BigDecimal salePrice; + /** + * 报价方式:1_按毛重报价;2_按净重报价; + */ + @Schema(title = "报价方式:1_按毛重报价;2_按净重报价;") + private PurchaseOrderPricingMethodEnum pricingMethod; + /** * 发票金额 */ @@ -140,7 +153,7 @@ public class OrderSupplier extends Command { * 发票 */ @Schema(title = "发票") - private List invoiceImg; + private List invoiceImg; /** * 是否上传合同 @@ -152,7 +165,7 @@ public class OrderSupplier extends Command { * 合同 */ @Schema(title = "合同") - private List contractImg; + private List contractImg; /** * 产品ID diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/ShipOrderPackage.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/ShipOrderPackage.java index 6230d1f..77395b3 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/ShipOrderPackage.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/ShipOrderPackage.java @@ -31,7 +31,7 @@ public class ShipOrderPackage extends DTO { /** * 箱型ID */ - @Schema(title = "箱型ID") + @Schema(title = "箱型ID", type = "string", requiredMode = Schema.RequiredMode.REQUIRED) private Long boxSpecId; /** diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/SupplierPackageUsage.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/SupplierPackageUsage.java new file mode 100644 index 0000000..2ef54f7 --- /dev/null +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/SupplierPackageUsage.java @@ -0,0 +1,22 @@ +package com.xunhong.erp.turbo.api.biz.dto.common; + +import com.alibaba.cola.dto.Command; +import com.xunhong.erp.turbo.api.biz.dto.enums.OrderPackageBoxTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author shenyifei + */ +@Data +@Schema(title = "采购订单供应商纸箱使用情况") +@EqualsAndHashCode(callSuper = true) +public class SupplierPackageUsage extends Command { + + @Schema(title = "箱子类型:1_本次使用;2_额外运输;3_已使用额外运输;4_车上剩余;5_瓜农纸箱;6_空箱;") + private OrderPackageBoxTypeEnum boxType; + + @Schema(title = "是否使用:0_未回答;1_使用;2_未使用") + private Integer isUsed; +} diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/UploadFileItem.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/UploadFileItem.java new file mode 100644 index 0000000..c1798de --- /dev/null +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/common/UploadFileItem.java @@ -0,0 +1,26 @@ +package com.xunhong.erp.turbo.api.biz.dto.common; + +import com.alibaba.cola.dto.DTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author shenyifei + */ +@Data +@Schema(title = "文件上传") +@EqualsAndHashCode(callSuper = true) +public class UploadFileItem extends DTO { + @Schema(title = "文件名") + private String fileName; + + @Schema(title = "文件路径") + private String filePath; + + @Schema(title = "文件大小") + private Long fileSize; + + @Schema(title = "文件类型") + private String fileType; +} diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/OrderSupplierVO.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/OrderSupplierVO.java index 85cf1ef..d01d027 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/OrderSupplierVO.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/OrderSupplierVO.java @@ -2,6 +2,7 @@ package com.xunhong.erp.turbo.api.biz.dto.vo; import com.alibaba.cola.dto.DTO; import com.xunhong.erp.turbo.api.biz.dto.common.OrderVehicle; +import com.xunhong.erp.turbo.api.biz.dto.common.UploadFileItem; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -172,7 +173,7 @@ public class OrderSupplierVO extends DTO { * 发票照片 */ @Schema(title = "发票照片") - private List invoiceImg; + private List invoiceImg; /** * 是否上传合同 @@ -184,7 +185,7 @@ public class OrderSupplierVO extends DTO { * 合同照片 */ @Schema(title = "合同照片") - private List contractImg; + private List contractImg; /** * 创建时间 diff --git a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/PurchaseOrderVO.java b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/PurchaseOrderVO.java index 122882f..9fefb74 100644 --- a/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/PurchaseOrderVO.java +++ b/erp-turbo-common/erp-turbo-api/src/main/java/com/xunhong/erp/turbo/api/biz/dto/vo/PurchaseOrderVO.java @@ -9,7 +9,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -63,60 +62,6 @@ public class PurchaseOrderVO extends DTO { @Schema(title = "报价方式:1_按毛重报价;2_按净重报价;") private PurchaseOrderPricingMethodEnum pricingMethod; - /** - * 销售金额 - */ - @Schema(title = "销售金额") - private BigDecimal saleAmount; - - /** - * 包装费 - */ - @Schema(title = "包装费") - private BigDecimal packageFee; - - /** - * 平均单价(元/斤) - */ - @Schema(title = "平均单价(元/斤)") - private BigDecimal avgUnitPrice; - - /** - * 是否返点 - */ - @Schema(title = "是否返点") - private Boolean rebate; - - /** - * 毛重(斤) - */ - @Schema(title = "毛重(斤)") - private BigDecimal grossWeight; - - /** - * 净重(斤) - */ - @Schema(title = "净重(斤)") - private BigDecimal netWeight; - - /** - * 成本合计 - */ - @Schema(title = "成本合计") - private BigDecimal totalCost; - - /** - * 运费 - */ - @Schema(title = "运费") - private BigDecimal freightCharge; - - /** - * 瓜农数量 - */ - @Schema(title = "瓜农数量") - private Integer supplierCount; - /** * 采购订单状态: 0_草稿;1_审核中;2_已完成;3_已驳回;4_已关闭; */ diff --git a/erp-turbo-common/erp-turbo-base/src/main/java/com/xunhong/erp/turbo/base/utils/FileUploadUtil.java b/erp-turbo-common/erp-turbo-base/src/main/java/com/xunhong/erp/turbo/base/utils/FileUploadUtil.java new file mode 100644 index 0000000..a36cf71 --- /dev/null +++ b/erp-turbo-common/erp-turbo-base/src/main/java/com/xunhong/erp/turbo/base/utils/FileUploadUtil.java @@ -0,0 +1,177 @@ +package com.xunhong.erp.turbo.base.utils; + +import cn.hutool.core.date.LocalDateTimeUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 文件上传工具类 + * + * @author system + */ +@Slf4j +public class FileUploadUtil { + + /** + * 安全获取文件名(处理后缀问题) + * + * @param file 上传的文件 + * @return 处理后的安全文件名 + */ + public static String getSafeFileName(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + + log.debug("原始文件名: {}, Content-Type: {}", originalFilename, file.getContentType()); + + // 如果原始文件名为空或无效,生成默认文件名 + if (StringUtils.isBlank(originalFilename)) { + String generatedName = generateDefaultFileName(file); + log.warn("原始文件名为空,生成文件名: {}", generatedName); + return generatedName; + } + + // 检查文件名是否包含后缀 + if (!containsFileExtension(originalFilename)) { + // 尝试从Content-Type推断文件类型 + String extension = getExtensionFromContentType(file.getContentType()); + if (extension != null) { + String fileNameWithExt = originalFilename + "." + extension; + log.info("文件名缺少后缀,从Content-Type推断: {} -> {}", originalFilename, fileNameWithExt); + return fileNameWithExt; + } else { + log.warn("无法推断文件后缀,使用原始文件名: {}", originalFilename); + } + } + + return originalFilename; + } + + /** + * 生成唯一的文件对象名 + * + * @param file 上传的文件 + * @return 生成的对象名 + */ + public static String generateObjectName(MultipartFile file) { + String safeFileName = getSafeFileName(file); + String datePath = LocalDateTimeUtil.format(LocalDateTime.now(), "yyMM/dd"); + return "uploads/" + datePath + "/" + safeFileName; + } + + /** + * 检查文件名是否包含后缀 + * + * @param filename 文件名 + * @return 是否包含后缀 + */ + private static boolean containsFileExtension(String filename) { + if (filename == null) return false; + int lastDotIndex = filename.lastIndexOf('.'); + return lastDotIndex > 0 && lastDotIndex < filename.length() - 1; + } + + /** + * 从Content-Type推断文件扩展名 + * + * @param contentType MIME类型 + * @return 文件扩展名,如果无法推断则返回null + */ + private static String getExtensionFromContentType(String contentType) { + if (contentType == null) return null; + + switch (contentType.toLowerCase()) { + // 图片类型 + case "image/jpeg": + return "jpeg"; + case "image/jpg": + return "jpg"; + case "image/png": + return "png"; + case "image/gif": + return "gif"; + case "image/webp": + return "webp"; + case "image/bmp": + return "bmp"; + case "image/svg+xml": + return "svg"; + + // 文档类型 + case "application/pdf": + return "pdf"; + case "application/msword": + return "doc"; + case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + return "docx"; + case "application/vnd.ms-excel": + return "xls"; + case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + return "xlsx"; + + // 文本类型 + case "text/plain": + return "txt"; + case "text/csv": + return "csv"; + case "application/json": + return "json"; + case "application/xml": + return "xml"; + + // 压缩文件 + case "application/zip": + return "zip"; + case "application/x-rar-compressed": + return "rar"; + + default: + log.debug("未知的Content-Type: {}", contentType); + return null; + } + } + + /** + * 生成默认文件名 + * + * @param file 上传的文件 + * @return 生成的文件名 + */ + private static String generateDefaultFileName(MultipartFile file) { + String timestamp = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss"); + String randomId = UUID.randomUUID().toString().substring(0, 8); + String extension = getExtensionFromContentType(file.getContentType()); + + return extension != null ? + "file_" + timestamp + "_" + randomId + "." + extension : + "file_" + timestamp + "_" + randomId; + } + + /** + * 验证文件类型是否允许上传 + * + * @param file 上传的文件 + * @param allowedExtensions 允许的文件扩展名数组 + * @return 是否允许上传 + */ + public static boolean isAllowedFileType(MultipartFile file, String[] allowedExtensions) { + String fileName = getSafeFileName(file); + if (fileName == null) return false; + + int lastDotIndex = fileName.lastIndexOf('.'); + if (lastDotIndex == -1) return false; + + String extension = fileName.substring(lastDotIndex + 1).toLowerCase(); + + for (String allowedExt : allowedExtensions) { + if (allowedExt.toLowerCase().equals(extension)) { + return true; + } + } + + return false; + } +}