Browse Source

Merge remote-tracking branch 'origin/master'

lixiaolong 3 months ago
parent
commit
4912d6a1f7

+ 5 - 0
commons/src/main/java/com/jzg/commons/entity/finance/po/InsPlyIncome.java

@@ -10,6 +10,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serial;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @EqualsAndHashCode(callSuper = true)
@@ -341,5 +342,9 @@ public class InsPlyIncome extends BaseModel {
     @ExcelIgnore
     private String jyTotalProportion;
 
+    @TableField(exist = false)
+    @Schema(description = "非车应收金额")
+    private BigDecimal jyTotalPremiums;
+
 
 }

+ 63 - 0
commons/src/main/java/com/jzg/commons/entity/orders/vo/FeeAuditVo.java

@@ -2,6 +2,7 @@ package com.jzg.commons.entity.orders.vo;
 
 import com.alibaba.excel.annotation.ExcelIgnore;
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.jzg.commons.core.base.BaseModel;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -352,4 +353,66 @@ public class FeeAuditVo extends BaseModel {
     @ExcelProperty("权益相关:可用金额<可用金额=出口费用➖出口人留点费用>")
     @Schema(description = "权益相关:可用金额<可用金额=出口费用➖出口人留点费用>")
     private BigDecimal availableAmount;
+
+
+    @Schema(description = "一级分销费")
+    @ExcelProperty("一级分销费")
+    @TableField(exist = false)
+    private BigDecimal firstDistributionFee;
+
+//    @Schema(description = "一级分销员id")
+//    @ExcelProperty("订单号")
+//    @TableField(exist = false)
+//    private String firstDistributorId;
+//
+//    @Schema(description = "一级分销员")
+//    @ExcelProperty("订单号")
+//    @TableField(exist = false)
+//    private String firstDistributorName;
+
+    @Schema(description = "一级分销比例")
+    @ExcelProperty("一级分销比例")
+    @TableField(exist = false)
+    private BigDecimal firstDistributionRatio;
+
+    @Schema(description = "二级分销费")
+    @ExcelProperty("二级分销费")
+    @TableField(exist = false)
+    private BigDecimal secondDistributionFee;
+
+//    @Schema(description = "二级分销员id")
+//    @ExcelProperty("二级分销员id")
+//    @ExcelIgnore
+//    @TableField(exist = false)
+//    private String secondDistributorId;
+//
+//    @Schema(description = "二级分销员")
+//    @ExcelProperty("二级分销员")
+//    @TableField(exist = false)
+//    private String secondDistributorName;
+
+    @Schema(description = "二级分销比例")
+    @ExcelProperty("二级分销比例")
+    @TableField(exist = false)
+    private BigDecimal secondDistributionRatio;
+
+    @Schema(description = "三级分销费")
+    @ExcelProperty("三级分销费")
+    @TableField(exist = false)
+    private BigDecimal thirdDistributionFee;
+
+//    @Schema(description = "三级分销员id")
+//    @ExcelIgnore
+//    @TableField(exist = false)
+//    private String thirdDistributorId;
+//
+//    @Schema(description = "三级分销员")
+//    @ExcelProperty("三级分销员")
+//    @TableField(exist = false)
+//    private String thirdDistributorName;
+
+    @Schema(description = "三级分销比例")
+    @ExcelProperty("三级分销比例")
+    @TableField(exist = false)
+    private BigDecimal thirdDistributionRatio;
 }

+ 189 - 38
commons/src/main/java/com/jzg/commons/util/ImageUtil.java

@@ -126,10 +126,16 @@ public class ImageUtil {
 
     /**
      * 图片压缩参数配置
+     * - MAX_WIDTH/MAX_HEIGHT: 图片最大尺寸限制,超过则按比例缩放
+     * - INITIAL_QUALITY: 初始压缩质量(0.0-1.0),值越高质量越好但文件越大
+     * - MIN_QUALITY: 最低压缩质量,达到此值后停止迭代
+     * - QUALITY_STEP: 每次迭代降低的质量步长
      */
     private static final int MAX_WIDTH = 1920;
     private static final int MAX_HEIGHT = 1080;
-    private static final float QUALITY = 0.7f;
+    private static final float INITIAL_QUALITY = 0.7f;
+    private static final float MIN_QUALITY = 0.1f;
+    private static final float QUALITY_STEP = 0.1f;
 
     /**
      * 判断文件是否为图片类型(通过 Content-Type 判断)
@@ -158,70 +164,175 @@ public class ImageUtil {
 
     /**
      * 图片压缩方法
+     * 
      * 压缩策略:
-     * 1. 如果图片尺寸超过 MAX_WIDTH x MAX_HEIGHT,先按比例缩放
-     * 2. 无论是否缩放,都进行质量压缩(压缩质量为 QUALITY)
-     * 3. 保持原图片格式,PNG 保持透明通道
+     * 1. 尺寸压缩:如果图片尺寸超过 MAX_WIDTH x MAX_HEIGHT,先按比例缩放至最大尺寸以内
+     * 2. 迭代质量压缩:从 INITIAL_QUALITY(0.7) 开始逐步降低质量,直到压缩后文件小于原图或达到 MIN_QUALITY(0.1)
+     * 3. 格式降级:如果PNG格式压缩无效,自动尝试转为JPEG格式进行有损压缩
+     * 4. 透明通道处理:转为JPEG时,将透明通道填充为白色背景
      *
      * @param file 上传的图片文件
-     * @return 压缩后的字节数组,压缩失败返回 null
+     * @return 压缩后的字节数组,压缩失败或压缩后更大则返回 null(调用方应使用原文件)
      */
     public static byte[] compressImage(MultipartFile file) {
+        // 参数校验:文件为空直接返回null
         if (file == null || file.isEmpty()) {
             return null;
         }
 
+        // 获取原图大小,用于后续压缩效果对比
+        long originalSize = file.getSize();
+
         try (InputStream is = file.getInputStream()) {
+            // 读取图片为BufferedImage对象
             BufferedImage originalImage = ImageIO.read(is);
             if (originalImage == null) {
+                log.error("无法读取图片,ImageIO.read返回null,可能是不支持的图片格式");
                 return null;
             }
 
+            // 获取图片原始尺寸
             int width = originalImage.getWidth();
             int height = originalImage.getHeight();
-
-            // 如果图片尺寸在限制范围内,直接进行质量压缩
-            if (width <= MAX_WIDTH && height <= MAX_HEIGHT) {
-                return compressWithQuality(originalImage, file.getOriginalFilename());
+            log.debug("图片原始尺寸: {} x {}, 文件大小: {} bytes", width, height, originalSize);
+
+            // 默认使用原图,如需缩放则替换
+            BufferedImage targetImage = originalImage;
+
+            // 步骤1:尺寸压缩 - 超过最大尺寸则按比例缩放
+            if (width > MAX_WIDTH || height > MAX_HEIGHT) {
+                // 计算缩放比例,保持宽高比,取最小比例确保不超过任一维度限制
+                double scale = Math.min((double) MAX_WIDTH / width, (double) MAX_HEIGHT / height);
+                int newWidth = (int) (width * scale);
+                int newHeight = (int) (height * scale);
+
+                // 创建缩放后的图片,使用双线性插值保证清晰度
+                targetImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
+                Graphics2D g2d = targetImage.createGraphics();
+                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
+                g2d.dispose();
+                log.debug("图片缩放后尺寸: {} x {}", newWidth, newHeight);
             }
 
-            // 计算缩放比例,保持宽高比
-            double scale = Math.min((double) MAX_WIDTH / width, (double) MAX_HEIGHT / height);
-            int newWidth = (int) (width * scale);
-            int newHeight = (int) (height * scale);
+            // 获取图片格式(从文件名解析)
+            String format = getImageFormat(file.getOriginalFilename());
+            
+            // 步骤2:迭代质量压缩,尝试不同质量档位
+            byte[] compressedBytes = compressWithIterativeQuality(targetImage, format, originalSize);
 
-            // 创建缩放后的图片,使用双线性插值算法保证清晰度
-            BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
-            Graphics2D g2d = resizedImage.createGraphics();
-            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-            g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
-            g2d.dispose();
+            // 校验压缩结果:必须小于原图且不为空才视为有效压缩
+            if (compressedBytes != null && compressedBytes.length < originalSize) {
+                log.debug("图片压缩成功,原图: {} bytes, 压缩后: {} bytes", originalSize, compressedBytes.length);
+                return compressedBytes;
+            }
 
-            // 对缩放后的图片进行质量压缩
-            return compressWithQuality(resizedImage, file.getOriginalFilename());
+            // 压缩无效,记录警告日志并返回null(调用方应使用原文件)
+            log.warn("图片压缩无效,压缩后大小({} bytes) >= 原图大小({} bytes)",
+                    compressedBytes != null ? compressedBytes.length : 0, originalSize);
+            return null;
         } catch (IOException e) {
-            log.error("图片压缩失败", e);
+            log.error("图片压缩失败,读取或处理图片时发生IO异常", e);
             return null;
         }
     }
 
     /**
-     * 图片质量压缩
-     * 使用 ImageWriter 进行高质量压缩,保持原格式
-     *
-     * @param image    待压缩的图片对象
-     * @param filename 原文件名,用于确定图片格式
-     * @return 压缩后的字节数组
+     * 迭代质量压缩
+     * 
+     * 核心算法:从 INITIAL_QUALITY(0.7) 开始逐步降低质量,每次降低 QUALITY_STEP(0.1),
+     * 直到压缩后文件小于原图或达到 MIN_QUALITY(0.1)。
+     * 
+     * 特殊处理:
+     * - 如果某质量档位压缩失败(返回null),继续尝试下一档位,不直接退出
+     * - 如果PNG格式所有质量档位均无效,自动尝试转为JPEG格式进行有损压缩
+     * 
+     * @param image        待压缩的图片对象(已完成尺寸缩放)
+     * @param format       图片格式(如 "jpg", "png")
+     * @param originalSize 原图大小(字节),用于判断压缩是否有效
+     * @return 压缩后的字节数组,若所有档位均无效则返回 null
+     */
+    private static byte[] compressWithIterativeQuality(BufferedImage image, String format, long originalSize) {
+        log.info("开始迭代压缩,格式: {}, 原图大小: {} bytes", format, originalSize);
+        
+        // 从初始质量开始迭代
+        float quality = INITIAL_QUALITY;
+
+        // 迭代循环:从高到低尝试不同质量档位
+        while (quality >= MIN_QUALITY) {
+            // 使用当前质量档位进行压缩
+            byte[] compressedBytes = compressWithQuality(image, format, quality);
+
+            // 压缩失败(返回null),继续尝试下一质量档位
+            if (compressedBytes == null) {
+                log.warn("compressWithQuality返回null, 格式: {}, 质量: {}", format, quality);
+                quality -= QUALITY_STEP;
+                continue;
+            }
+
+            // 记录当前压缩尝试结果
+            log.info("压缩尝试 - 格式: {}, 质量: {}, 压缩后大小: {} bytes", format, quality, compressedBytes.length);
+
+            // 判断压缩是否有效:压缩后大小 > 0 且 < 原图大小
+            if (compressedBytes.length > 0 && compressedBytes.length < originalSize) {
+                log.info("压缩成功,使用质量 {},压缩后大小 {} bytes", quality, compressedBytes.length);
+                return compressedBytes;
+            }
+
+            // 压缩无效,降低质量继续尝试
+            quality -= QUALITY_STEP;
+        }
+
+        // 所有质量档位均无效
+        log.warn("所有质量档位均无效,格式: {}", format);
+
+        // PNG格式特殊处理:尝试转为JPEG格式进行有损压缩
+        // JPEG的有损压缩通常能获得更好的压缩率,适合对透明度要求不高的场景
+        if ("png".equalsIgnoreCase(format)) {
+            log.info("尝试将PNG转为JPEG格式压缩");
+            byte[] jpegBytes = compressWithIterativeQuality(image, "jpg", originalSize);
+            if (jpegBytes != null && jpegBytes.length > 0 && jpegBytes.length < originalSize) {
+                log.info("PNG转JPEG压缩成功,原图: {} bytes, 压缩后: {} bytes", originalSize, jpegBytes.length);
+                return jpegBytes;
+            }
+            log.warn("PNG转JPEG压缩也失败");
+        }
+
+        // 所有压缩尝试均无效,返回null
+        return null;
+    }
+
+    /**
+     * 图片质量压缩(指定质量参数)
+     * 
+     * 使用 ImageWriter 进行高质量压缩,支持以下特性:
+     * 1. 透明通道处理:当输出格式为JPEG且原图有透明通道时,先转为白色背景的不透明图片
+     * 2. 压缩参数配置:通过 ImageWriteParam 设置显式压缩质量
+     * 3. 降级处理:如果 ImageWriter 不可用,使用 ImageIO.write 作为回退方案
+     * 
+     * @param image   待压缩的图片对象
+     * @param format  图片格式(如 "jpg", "png")
+     * @param quality 压缩质量(0.0-1.0),值越高质量越好但文件越大
+     * @return 压缩后的字节数组,所有方案均失败则返回 null
      */
-    private static byte[] compressWithQuality(BufferedImage image, String filename) {
+    private static byte[] compressWithQuality(BufferedImage image, String format, float quality) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try {
-            String format = getImageFormat(filename);
+            // 默认使用原图作为输出
+            BufferedImage outputImage = image;
+
+            // 特殊处理:JPEG格式不支持透明通道,需要先转为不透明图片
+            // 将透明区域填充为白色背景,避免透明部分显示为黑色
+            if ("jpg".equalsIgnoreCase(format) && image.getTransparency() != Transparency.OPAQUE) {
+                outputImage = convertToOpaque(image);
+            }
+
+            // 获取对应格式的 ImageWriter
             Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
 
-            // 如果找不到对应格式的 ImageWriter,使用默认方式写入
+            // 如果找不到 ImageWriter,使用 ImageIO.write 作为回退方案
             if (!writers.hasNext()) {
-                ImageIO.write(image, format, baos);
+                ImageIO.write(outputImage, format, baos);
                 return baos.toByteArray();
             }
 
@@ -229,29 +340,69 @@ public class ImageUtil {
             ImageWriter writer = writers.next();
             ImageWriteParam writeParam = writer.getDefaultWriteParam();
 
+            // 设置显式压缩模式和质量参数
             if (writeParam.canWriteCompressed()) {
                 writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
-                writeParam.setCompressionQuality(QUALITY);
+                writeParam.setCompressionQuality(quality);
             }
 
+            // 写入压缩后的图片数据
             writer.setOutput(ImageIO.createImageOutputStream(baos));
-            writer.write(null, new javax.imageio.IIOImage(image, null, null), writeParam);
+            writer.write(null, new javax.imageio.IIOImage(outputImage, null, null), writeParam);
             writer.dispose();
 
-            return baos.toByteArray();
+            // 获取压缩结果并记录日志
+            byte[] result = baos.toByteArray();
+            log.debug("compressWithQuality完成 - 格式: {}, 质量: {}, 结果大小: {} bytes", format, quality, result.length);
+            return result;
         } catch (IOException e) {
-            log.error("图片质量压缩失败", e);
+            // 压缩失败,记录错误日志并尝试回退方案
+            log.error("图片质量压缩失败,格式: {}, 质量: {}", format, quality, e);
             try {
-                // 压缩失败时回退到普通写入方式
-                ImageIO.write(image, getImageFormat(filename), baos);
+                // 回退:使用 ImageIO.write 直接写入(不设置压缩参数)
+                ImageIO.write(image, format, baos);
                 return baos.toByteArray();
             } catch (IOException ex) {
+                // 回退也失败,返回null
                 log.error("图片回退保存失败", ex);
                 return null;
             }
         }
     }
 
+    /**
+     * 将带透明通道的图片转换为不透明图片
+     * 
+     * JPEG格式不支持透明通道,当需要将PNG等带透明通道的图片转为JPEG时,
+     * 需要先将透明区域填充为白色背景,避免透明部分显示为黑色。
+     * 
+     * @param image 带透明通道的图片
+     * @return 不透明图片(TYPE_INT_RGB),透明区域已填充为白色
+     */
+    private static BufferedImage convertToOpaque(BufferedImage image) {
+        // 创建不透明的RGB图片(TYPE_INT_RGB不支持透明通道)
+        BufferedImage opaqueImage = new BufferedImage(
+                image.getWidth(),
+                image.getHeight(),
+                BufferedImage.TYPE_INT_RGB
+        );
+        
+        // 创建 Graphics2D 对象用于绘制
+        Graphics2D g2d = opaqueImage.createGraphics();
+        
+        // 步骤1:填充白色背景,覆盖透明区域
+        g2d.setColor(Color.WHITE);
+        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
+        
+        // 步骤2:绘制原图,此时透明区域已被白色背景覆盖
+        g2d.drawImage(image, 0, 0, null);
+        
+        // 释放资源
+        g2d.dispose();
+        
+        return opaqueImage;
+    }
+
     /**
      * 根据文件名获取图片格式
      *

+ 11 - 6
platform/src/main/java/com/jzg/controller/FileUploadController.java

@@ -60,6 +60,7 @@ public class FileUploadController extends BaseController {
             // 获取文件原始名称和扩展名
             String originalFileName = file.getOriginalFilename();
             String extension = getFileExtension(originalFileName);
+            long originalSize = file.getSize();
 
             // 判断是否为图片文件,若是则进行压缩
             // 双重校验:通过 Content-Type 和文件扩展名判断
@@ -68,19 +69,25 @@ public class FileUploadController extends BaseController {
                 compressedBytes = ImageUtil.compressImage(file);
             }
 
-            // 根据是否压缩成功,计算文件的 MD5 和 ETag
-            // 注意:MD5/ETag 必须基于最终上传的文件内容计算,确保与 MinIO 存储一致
+            if (compressedBytes != null && compressedBytes.length > 0) {
+                System.out.printf("图片压缩前大小: %d bytes, 压缩后大小: %d bytes%n", originalSize, compressedBytes.length);
+
+                if (compressedBytes.length >= originalSize) {
+                    System.out.printf("警告:压缩后图片(%d bytes)比原图(%d bytes)大,使用原图片上传%n", compressedBytes.length, originalSize);
+                    compressedBytes = null;
+                }
+            }
+
             String fileMd5;
             String fileEtag;
             String objectName;
 
             if (compressedBytes != null && compressedBytes.length > 0) {
-                // 使用压缩后的字节数组计算哈希值
+                // 生成对象名
                 fileMd5 = FileUtils.getFileMD5(compressedBytes);
                 fileEtag = FileUtils.getFileETag(compressedBytes);
                 objectName = generateObjectName(originalFileName, fileMd5, extension);
             } else {
-                // 使用原始文件计算哈希值(非图片或压缩失败时回退)
                 fileMd5 = FileUtils.getFileMD5(file);
                 fileEtag = FileUtils.getFileETag(file);
                 objectName = generateObjectName(originalFileName, fileMd5, extension);
@@ -93,10 +100,8 @@ public class FileUploadController extends BaseController {
             // 根据是否压缩成功,选择对应的上传方式
             SysUploadFiles sysUploadFiles;
             if (compressedBytes != null && compressedBytes.length > 0) {
-                // 上传压缩后的图片字节数组
                 sysUploadFiles = uploadFileToMinIo(compressedBytes, objectName, fileEtag, extension, file.getContentType());
             } else {
-                // 上传原始文件(非图片或压缩失败时回退)
                 sysUploadFiles = uploadFileToMinIo(file, objectName, fileEtag, extension);
             }
             return HttpResult.ok(sysUploadFiles);

+ 2 - 2
tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/service/impl/AnShengCrawlerRequestImpl.java

@@ -250,8 +250,8 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
         paymentLinkResultsVo = loopAcquisitionPaymentLink(paymentLinkVo);
         // 支付链接为空直接失败返回
         if (StringUtils.isEmpty(paymentLinkResultsVo.getPaymentLink())) {
-            underwritingResultsVo.setUnderwritingResult(applyApplicationResponse.getMessage());
-            underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
+            underwritingResultsVo.setUnderwritingResult("核保失败,转人工");
+            underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.STAFF_PROCESS);
             return underwritingResultsVo;
         }
         // 正常赋值逻辑

+ 1 - 1
tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java

@@ -3349,7 +3349,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         // 设置应收
         updateIncome.setJqReceivablePremium(jqInletPremium.toString());
         updateIncome.setSyReceivablePremium(syInletPremium.toString());
-        updateIncome.setJyReceivablePremium(jyPremium.toString());
+        updateIncome.setJyReceivablePremium(jyInletPremium.toString());
 
         // 设置手续费比例
         updateIncome.setJqSuperviseCostsProportion(BigDecimalUtil.toString(ratioVo.getJqCommissionRatio()));

+ 3 - 1
tenant/organization/src/main/resources/mapper/ReceivableMapper.xml

@@ -239,6 +239,7 @@
         ipi.jq_other_costs_premiums,
         ipi.sy_other_costs_premiums,
         ipi.jy_other_costs_premiums,
+        COALESCE(ipi.jy_supervise_costs_premiums, 0) + COALESCE(ipi.jy_other_costs_premiums,0) as jyTotalPremiums,
         io.product_name,
         iocui.name AS insuredPerson,
         ipi.jq_premium,
@@ -248,6 +249,7 @@
         ipi.jq_receivable_premium,
         ipi.sy_receivable_premium,
         ipi.jy_receivable_premium,
+
         ipi.jq_supervise_costs_proportion,
         ipi.sy_supervise_costs_proportion,
         ipi.jy_supervise_costs_proportion,
@@ -2078,7 +2080,7 @@
         LEFT JOIN ins_ply_income_invoice_settlement sett ON ipii.id = sett.invoice_id AND sett.is_delete = 0
         left join ins_orders io on ipi.order_no = io.id
         <where>
-            ipi.is_delete = 0
+            ipi.is_delete = 0  and ipi.agreement_type = 1
             <if test="query.companyId != null and query.companyId != ''">
                 AND ipi.company_id = #{query.companyId}
             </if>