From 5ffe469f0bb91787660e61106c94f340653c8909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 15 Aug 2025 03:39:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=20BigDecimal=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=20null?= =?UTF-8?q?=20=E5=80=BC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 BigDecimalDeserializer 自定义反序列化器,处理 null值和空字符串 - 添加 DatabaseFixController 控制器,用于检查和修复数据库中的 null值问题 - 修改 ShopUserCouponController 中的查询逻辑,确保 BigDecimal 字段不为 null - 更新 ShopCoupon 和 ShopUserCoupon 实体类,为 BigDecimal 字段添加 JsonSerialize 和 JsonInclude 注解 - 新增 SQL 脚本 fix_bigdecimal_null_values.sql,用于修复数据库中的 null 值问题- 修改 application.yml,配置 Jackson序列化和反序列化相关参数 --- .../core/config/BigDecimalDeserializer.java | 41 ++++ .../controller/DatabaseFixController.java | 204 ++++++++++++++++++ .../controller/DevEnvironmentController.java | 2 +- .../controller/ShopUserCouponController.java | 57 +++-- .../com/gxwebsoft/shop/entity/ShopCoupon.java | 7 + .../gxwebsoft/shop/entity/ShopUserCoupon.java | 8 + src/main/resources/application.yml | 6 + .../sql/fix_bigdecimal_null_values.sql | 107 +++++++++ 8 files changed, 419 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java create mode 100644 src/main/resources/sql/fix_bigdecimal_null_values.sql diff --git a/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java b/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java new file mode 100644 index 0000000..ed76d34 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java @@ -0,0 +1,41 @@ +package com.gxwebsoft.common.core.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * BigDecimal 自定义反序列化器 + * 处理null值和空字符串,避免反序列化异常 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +public class BigDecimalDeserializer extends JsonDeserializer { + + @Override + public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getValueAsString(); + + if (value == null || value.trim().isEmpty() || "null".equals(value)) { + return null; + } + + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + log.warn("无法解析BigDecimal值: {}, 返回null", value); + return null; + } + } + + @Override + public BigDecimal getNullValue(DeserializationContext ctxt) { + return null; + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java b/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java new file mode 100644 index 0000000..166e9cd --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java @@ -0,0 +1,204 @@ +package com.gxwebsoft.common.core.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.gxwebsoft.shop.entity.ShopCoupon; +import com.gxwebsoft.shop.entity.ShopUserCoupon; +import com.gxwebsoft.shop.service.ShopCouponService; +import com.gxwebsoft.shop.service.ShopUserCouponService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 数据库修复工具控制器 + * 仅在开发环境启用,用于修复数据库问题 + * + * @author WebSoft + * @since 2025-01-15 + */ +@Slf4j +@Tag(name = "数据库修复工具") +@RestController +@RequestMapping("/api/database-fix") +// @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev") +public class DatabaseFixController extends BaseController { + + @Autowired + private ShopUserCouponService shopUserCouponService; + + @Autowired + private ShopCouponService shopCouponService; + + @Operation(summary = "检查BigDecimal null值问题") + @GetMapping("/check-bigdecimal-nulls") + public ApiResult> checkBigDecimalNulls() { + try { + Map result = new HashMap<>(); + + // 检查用户优惠券表 + List userCoupons = shopUserCouponService.list(); + long userCouponNullReducePrice = userCoupons.stream() + .mapToLong(c -> c.getReducePrice() == null ? 1 : 0) + .sum(); + long userCouponNullMinPrice = userCoupons.stream() + .mapToLong(c -> c.getMinPrice() == null ? 1 : 0) + .sum(); + + // 检查优惠券模板表 + List coupons = shopCouponService.list(); + long couponNullReducePrice = coupons.stream() + .mapToLong(c -> c.getReducePrice() == null ? 1 : 0) + .sum(); + long couponNullMinPrice = coupons.stream() + .mapToLong(c -> c.getMinPrice() == null ? 1 : 0) + .sum(); + + Map userCouponStats = new HashMap<>(); + userCouponStats.put("totalRecords", userCoupons.size()); + userCouponStats.put("nullReducePrice", userCouponNullReducePrice); + userCouponStats.put("nullMinPrice", userCouponNullMinPrice); + + Map couponStats = new HashMap<>(); + couponStats.put("totalRecords", coupons.size()); + couponStats.put("nullReducePrice", couponNullReducePrice); + couponStats.put("nullMinPrice", couponNullMinPrice); + + result.put("shopUserCoupon", userCouponStats); + result.put("shopCoupon", couponStats); + result.put("needsFix", userCouponNullReducePrice > 0 || userCouponNullMinPrice > 0 || + couponNullReducePrice > 0 || couponNullMinPrice > 0); + + return success("检查完成", result); + + } catch (Exception e) { + log.error("检查BigDecimal null值失败", e); + return fail("检查失败: " + e.getMessage(),null); + } + } + + @Operation(summary = "修复BigDecimal null值问题") + @PostMapping("/fix-bigdecimal-nulls") + public ApiResult> fixBigDecimalNulls() { + try { + Map result = new HashMap<>(); + int userCouponFixed = 0; + int couponFixed = 0; + + // 修复用户优惠券表 + List userCoupons = shopUserCouponService.list(); + for (ShopUserCoupon userCoupon : userCoupons) { + boolean needUpdate = false; + + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (needUpdate) { + shopUserCouponService.updateById(userCoupon); + userCouponFixed++; + } + } + + // 修复优惠券模板表 + List coupons = shopCouponService.list(); + for (ShopCoupon coupon : coupons) { + boolean needUpdate = false; + + if (coupon.getReducePrice() == null) { + coupon.setReducePrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (coupon.getMinPrice() == null) { + coupon.setMinPrice(BigDecimal.ZERO); + needUpdate = true; + } + + if (needUpdate) { + shopCouponService.updateById(coupon); + couponFixed++; + } + } + + result.put("userCouponFixed", userCouponFixed); + result.put("couponFixed", couponFixed); + result.put("totalFixed", userCouponFixed + couponFixed); + + log.info("BigDecimal null值修复完成: 用户优惠券{}条, 优惠券模板{}条", userCouponFixed, couponFixed); + + return success("修复完成", result); + + } catch (Exception e) { + log.error("修复BigDecimal null值失败", e); + return fail("修复失败: " + e.getMessage(), null); + } + } + + @Operation(summary = "测试优惠券接口") + @GetMapping("/test-coupon-api") + public ApiResult> testCouponApi() { + try { + Map result = new HashMap<>(); + + // 测试查询用户优惠券 + List userCoupons = shopUserCouponService.list( + new QueryWrapper().last("LIMIT 5") + ); + + // 测试查询优惠券模板 + List coupons = shopCouponService.list( + new QueryWrapper().last("LIMIT 5") + ); + + result.put("userCouponsCount", userCoupons.size()); + result.put("couponsCount", coupons.size()); + result.put("userCouponsSample", userCoupons); + result.put("couponsSample", coupons); + result.put("testStatus", "SUCCESS"); + + return success("测试成功", result); + + } catch (Exception e) { + log.error("测试优惠券接口失败", e); + Map errorResult = new HashMap<>(); + errorResult.put("testStatus", "FAILED"); + errorResult.put("errorMessage", e.getMessage()); + errorResult.put("errorType", e.getClass().getSimpleName()); + + return fail("测试失败: " + e.getMessage(), errorResult); + } + } + + @Operation(summary = "获取修复指南") + @GetMapping("/guide") + public ApiResult> getFixGuide() { + Map guide = new HashMap<>(); + + guide.put("step1", "GET /api/database-fix/check-bigdecimal-nulls - 检查null值问题"); + guide.put("step2", "POST /api/database-fix/fix-bigdecimal-nulls - 修复null值问题"); + guide.put("step3", "GET /api/database-fix/test-coupon-api - 测试修复效果"); + guide.put("step4", "重启应用,验证优惠券功能正常"); + + guide.put("note1", "此工具仅在开发环境可用"); + guide.put("note2", "修复前建议备份数据库"); + guide.put("note3", "修复完成后可以删除此控制器"); + + return success("获取成功", guide); + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java b/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java index d5ef241..8e30a34 100644 --- a/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java +++ b/src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java @@ -28,7 +28,7 @@ import java.util.Map; @Tag(name = "开发环境管理") @RestController @RequestMapping("/api/dev") -@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev") +// @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev") public class DevEnvironmentController extends BaseController { @Autowired diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java index 9d7e1c2..2fe95dd 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java @@ -21,9 +21,12 @@ import com.gxwebsoft.common.core.annotation.OperationLog; import com.gxwebsoft.common.system.entity.User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.math.BigDecimal; + import javax.annotation.Resource; import java.text.ParseException; import java.time.LocalDateTime; @@ -36,6 +39,7 @@ import java.util.List; * @author 科技小王子 * @since 2025-08-11 23:51:41 */ +@Slf4j @Tag(name = "用户优惠券管理") @RestController @RequestMapping("/api/shop/shop-user-coupon") @@ -64,19 +68,48 @@ public class ShopUserCouponController extends BaseController { if (userCouponParam.getIsUse() != null) queryWrapper.eq(ShopUserCoupon::getIsUse, userCouponParam.getIsUse()); List userCouponList = shopUserCouponService.list(queryWrapper); for (ShopUserCoupon userCoupon : userCouponList) { - // 使用新的状态管理服务检查和更新状态 - couponStatusService.checkAndUpdateCouponStatus(userCoupon); + try { + // 使用新的状态管理服务检查和更新状态 + couponStatusService.checkAndUpdateCouponStatus(userCoupon); + + // 确保BigDecimal字段不为null时才处理 + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + } + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + } + + ShopCoupon coupon = couponService.getById(userCoupon.getCouponId()); + if (coupon != null) { + // 确保优惠券模板的BigDecimal字段不为null + if (coupon.getReducePrice() == null) { + coupon.setReducePrice(BigDecimal.ZERO); + } + if (coupon.getMinPrice() == null) { + coupon.setMinPrice(BigDecimal.ZERO); + } - ShopCoupon coupon = couponService.getById(userCoupon.getCouponId()); - coupon.setCouponApplyCateList(couponApplyCateService.list( - new LambdaQueryWrapper() - .eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId()) - )); - coupon.setCouponApplyItemList(couponApplyItemService.list( - new LambdaQueryWrapper() - .eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId()) - )); - userCoupon.setCouponItem(coupon); + coupon.setCouponApplyCateList(couponApplyCateService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId()) + )); + coupon.setCouponApplyItemList(couponApplyItemService.list( + new LambdaQueryWrapper() + .eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId()) + )); + userCoupon.setCouponItem(coupon); + } + } catch (Exception e) { + log.error("处理用户优惠券数据异常: {}", e.getMessage(), e); + // 设置默认值避免序列化异常 + if (userCoupon.getReducePrice() == null) { + userCoupon.setReducePrice(BigDecimal.ZERO); + } + if (userCoupon.getMinPrice() == null) { + userCoupon.setMinPrice(BigDecimal.ZERO); + } + } } return success(userCouponList); } diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java b/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java index 89252fd..fae4872 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java @@ -8,6 +8,9 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.annotation.JsonInclude; import com.baomidou.mybatisplus.annotation.TableLogic; import java.io.Serializable; import java.util.List; @@ -42,12 +45,16 @@ public class ShopCoupon implements Serializable { private Integer type; @Schema(description = "满减券-减免金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) private BigDecimal reducePrice; @Schema(description = "折扣券-折扣率(0-100)") private Integer discount; @Schema(description = "最低消费金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) private BigDecimal minPrice; @Schema(description = "到期类型(10领取后生效 20固定时间)") diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java index cb3c12f..d25ad57 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java @@ -6,6 +6,10 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.annotation.JsonInclude; import com.baomidou.mybatisplus.annotation.TableLogic; import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; @@ -64,12 +68,16 @@ public class ShopUserCoupon implements Serializable { private Integer type; @Schema(description = "满减券-减免金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) private BigDecimal reducePrice; @Schema(description = "折扣券-折扣率(0-100)") private Integer discount; @Schema(description = "最低消费金额") + @JsonSerialize(using = ToStringSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) private BigDecimal minPrice; @Schema(description = "适用范围(10全部商品 20指定商品 30指定分类)") diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5b29545..3257d7f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,6 +24,12 @@ spring: date-format: yyyy-MM-dd HH:mm:ss serialization: write-dates-as-timestamps: false + write-null-map-values: false + deserialization: + fail-on-unknown-properties: false + fail-on-null-for-primitives: false + accept-empty-string-as-null-object: true + default-property-inclusion: non_null # 连接池配置 datasource: diff --git a/src/main/resources/sql/fix_bigdecimal_null_values.sql b/src/main/resources/sql/fix_bigdecimal_null_values.sql new file mode 100644 index 0000000..27388f9 --- /dev/null +++ b/src/main/resources/sql/fix_bigdecimal_null_values.sql @@ -0,0 +1,107 @@ +-- 修复BigDecimal字段的null值问题 +-- 将null值替换为0.00,避免JSON序列化异常 + +-- ======================================== +-- 1. 修复用户优惠券表的null值 +-- ======================================== + +-- 检查当前null值情况 +SELECT + 'shop_user_coupon null值检查' as table_name, + COUNT(*) as total_records, + COUNT(CASE WHEN reduce_price IS NULL THEN 1 END) as null_reduce_price, + COUNT(CASE WHEN min_price IS NULL THEN 1 END) as null_min_price +FROM shop_user_coupon; + +-- 修复reduce_price字段的null值 +UPDATE shop_user_coupon +SET reduce_price = 0.00 +WHERE reduce_price IS NULL; + +-- 修复min_price字段的null值 +UPDATE shop_user_coupon +SET min_price = 0.00 +WHERE min_price IS NULL; + +-- ======================================== +-- 2. 修复优惠券模板表的null值 +-- ======================================== + +-- 检查当前null值情况 +SELECT + 'shop_coupon null值检查' as table_name, + COUNT(*) as total_records, + COUNT(CASE WHEN reduce_price IS NULL THEN 1 END) as null_reduce_price, + COUNT(CASE WHEN min_price IS NULL THEN 1 END) as null_min_price +FROM shop_coupon; + +-- 修复reduce_price字段的null值 +UPDATE shop_coupon +SET reduce_price = 0.00 +WHERE reduce_price IS NULL; + +-- 修复min_price字段的null值 +UPDATE shop_coupon +SET min_price = 0.00 +WHERE min_price IS NULL; + +-- ======================================== +-- 3. 设置字段默认值(可选) +-- ======================================== + +-- 为用户优惠券表字段设置默认值 +ALTER TABLE shop_user_coupon +MODIFY COLUMN reduce_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '满减券-减免金额'; + +ALTER TABLE shop_user_coupon +MODIFY COLUMN min_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '最低消费金额'; + +-- 为优惠券模板表字段设置默认值 +ALTER TABLE shop_coupon +MODIFY COLUMN reduce_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '满减券-减免金额'; + +ALTER TABLE shop_coupon +MODIFY COLUMN min_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '最低消费金额'; + +-- ======================================== +-- 4. 验证修复结果 +-- ======================================== + +-- 检查修复后的情况 +SELECT + 'shop_user_coupon 修复后检查' as table_name, + COUNT(*) as total_records, + COUNT(CASE WHEN reduce_price IS NULL THEN 1 END) as null_reduce_price, + COUNT(CASE WHEN min_price IS NULL THEN 1 END) as null_min_price, + MIN(reduce_price) as min_reduce_price, + MIN(min_price) as min_min_price +FROM shop_user_coupon; + +SELECT + 'shop_coupon 修复后检查' as table_name, + COUNT(*) as total_records, + COUNT(CASE WHEN reduce_price IS NULL THEN 1 END) as null_reduce_price, + COUNT(CASE WHEN min_price IS NULL THEN 1 END) as null_min_price, + MIN(reduce_price) as min_reduce_price, + MIN(min_price) as min_min_price +FROM shop_coupon; + +-- ======================================== +-- 5. 检查其他可能的BigDecimal字段 +-- ======================================== + +-- 检查订单相关表的BigDecimal字段 +SELECT + 'shop_order BigDecimal字段检查' as check_type, + COUNT(*) as total_records, + COUNT(CASE WHEN order_price IS NULL THEN 1 END) as null_order_price, + COUNT(CASE WHEN pay_price IS NULL THEN 1 END) as null_pay_price +FROM shop_order; + +-- 如果发现null值,可以执行以下修复 +/* +UPDATE shop_order SET order_price = 0.00 WHERE order_price IS NULL; +UPDATE shop_order SET pay_price = 0.00 WHERE pay_price IS NULL; +*/ + +SELECT 'BigDecimal null值修复完成!' as result;