Browse Source

fix(core): 修复 BigDecimal 字段反序列化 null 值问题

- 新增 BigDecimalDeserializer 自定义反序列化器,处理 null值和空字符串
- 添加 DatabaseFixController 控制器,用于检查和修复数据库中的 null值问题
- 修改 ShopUserCouponController 中的查询逻辑,确保 BigDecimal 字段不为 null
- 更新 ShopCoupon 和 ShopUserCoupon 实体类,为 BigDecimal 字段添加 JsonSerialize 和 JsonInclude 注解
- 新增 SQL 脚本 fix_bigdecimal_null_values.sql,用于修复数据库中的 null 值问题- 修改 application.yml,配置 Jackson序列化和反序列化相关参数
main
科技小王子 1 week ago
parent
commit
5ffe469f0b
  1. 41
      src/main/java/com/gxwebsoft/common/core/config/BigDecimalDeserializer.java
  2. 204
      src/main/java/com/gxwebsoft/common/core/controller/DatabaseFixController.java
  3. 2
      src/main/java/com/gxwebsoft/common/core/controller/DevEnvironmentController.java
  4. 57
      src/main/java/com/gxwebsoft/shop/controller/ShopUserCouponController.java
  5. 7
      src/main/java/com/gxwebsoft/shop/entity/ShopCoupon.java
  6. 8
      src/main/java/com/gxwebsoft/shop/entity/ShopUserCoupon.java
  7. 6
      src/main/resources/application.yml
  8. 107
      src/main/resources/sql/fix_bigdecimal_null_values.sql

41
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<BigDecimal> {
@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;
}
}

204
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<Map<String, Object>> checkBigDecimalNulls() {
try {
Map<String, Object> result = new HashMap<>();
// 检查用户优惠券表
List<ShopUserCoupon> 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<ShopCoupon> 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<String, Object> userCouponStats = new HashMap<>();
userCouponStats.put("totalRecords", userCoupons.size());
userCouponStats.put("nullReducePrice", userCouponNullReducePrice);
userCouponStats.put("nullMinPrice", userCouponNullMinPrice);
Map<String, Object> 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<Map<String, Object>> fixBigDecimalNulls() {
try {
Map<String, Object> result = new HashMap<>();
int userCouponFixed = 0;
int couponFixed = 0;
// 修复用户优惠券表
List<ShopUserCoupon> 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<ShopCoupon> 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<Map<String, Object>> testCouponApi() {
try {
Map<String, Object> result = new HashMap<>();
// 测试查询用户优惠券
List<ShopUserCoupon> userCoupons = shopUserCouponService.list(
new QueryWrapper<ShopUserCoupon>().last("LIMIT 5")
);
// 测试查询优惠券模板
List<ShopCoupon> coupons = shopCouponService.list(
new QueryWrapper<ShopCoupon>().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<String, Object> 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<Map<String, String>> getFixGuide() {
Map<String, String> 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);
}
}

2
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

57
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<ShopUserCoupon> 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<ShopCouponApplyCate>()
.eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId())
));
coupon.setCouponApplyItemList(couponApplyItemService.list(
new LambdaQueryWrapper<ShopCouponApplyItem>()
.eq(ShopCouponApplyItem::getCouponId, userCoupon.getCouponId())
));
userCoupon.setCouponItem(coupon);
coupon.setCouponApplyCateList(couponApplyCateService.list(
new LambdaQueryWrapper<ShopCouponApplyCate>()
.eq(ShopCouponApplyCate::getCouponId, userCoupon.getCouponId())
));
coupon.setCouponApplyItemList(couponApplyItemService.list(
new LambdaQueryWrapper<ShopCouponApplyItem>()
.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);
}

7
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固定时间)")

8
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指定分类)")

6
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:

107
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;
Loading…
Cancel
Save