运费微服务和redis存热点数据

news2025/1/16 4:45:02

目录

运费模板微服务

接收前端发送的模板实体类

插入数据时使用的entity类对象

 BaseEntity类

查询运费模板服务

新增和修改运费模块

整体流程

代码实现

运费计算 

整体流程

总的代码 

 查找运费模板方法

计算重量方法

Redis存入热点数据

1.从nacos导入共享redis配置

2.自定义RedisTemplate,自定义序列化和反序列化方式

 3.修改代码

4.存入数据时报错


运费模板微服务

接收前端发送的模板实体类

这里对实体类的每一个成员类变量使用了javax.validation.constraints注解来限制成员变量的值

package com.sl.ms.carriage.domain.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 运费模板对象
 */
@Data
public class CarriageDTO {

    /**
     * 运费模板id
     */
    @ApiModelProperty(value = "运费模板id")
    private Long id;

    /**
     * 模板类型:1-同城寄,2-省内寄,3-经济区互寄,4-跨省
     */
    @ApiModelProperty(value = "模板类型:1-同城寄,2-省内寄,3-经济区互寄,4-跨省", required = true)
    @Max(value = 4, message = "类型值必须是1、2、3、4")
    @Min(value = 1, message = "类型值必须是1、2、3、4")
    @NotNull(message = "模板类型不能为空")
    private Integer templateType;

    /**
     * 运送类型:1-普快,2-特快
     */
    @ApiModelProperty(value = "运送类型:1-普快,2-特快", required = true)
    @Max(value = 2, message = "类型值必须是1、2")
    @Min(value = 1, message = "类型值必须是1、2")
    @NotNull(message = "运送类型不能为空")
    private Integer transportType;

    /**
     * 关联城市:1-全国,2-京津冀,3-江浙沪,4-川渝,5-黑吉辽
     */

    @ApiModelProperty(value = "关联城市:1-全国,2-京津冀,3-江浙沪,4-川渝,5-黑吉辽", required = true)
    @NotNull(message = "关联城市不能为空")
    private List<String> associatedCityList;

    /**
     * 首重价格
     */
    @ApiModelProperty(value = "首重价格", required = true)
    @DecimalMin(value = "0.1", message = "首重价格必须大于0")
    @NotNull(message = "首重价格不能为空")
    private Double firstWeight;

    /**
     * 续重价格
     */
    @ApiModelProperty(value = "续重价格", required = true)
    @DecimalMin(value = "0.1", message = "续重价格必须大于0")
    @NotNull(message = "续重价格不能为空")
    private Double continuousWeight;

    /**
     * 轻抛系数
     */
    @ApiModelProperty(value = "轻抛系数", required = true)
    @Min(value = 1, message = "轻抛系数必须大于0")
    @NotNull(message = "轻抛系数不能为空")
    private Integer lightThrowingCoefficient;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime created;

    /**
     * 更新时间
     */
    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updated;

    @ApiModelProperty(value = "运费")
    private Double expense;

    @ApiModelProperty(value = "计费重量,单位:kg")
    private Double computeWeight;
}

插入数据时使用的entity类对象

@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@TableName("sl_carriage")
public class CarriageEntity extends BaseEntity {

    private static final long serialVersionUID = -5358753714676282742L;

    /**
     * 模板类型:1-同城寄,2-省内寄,3-经济区互寄,4-跨省
     */
    private Integer templateType;

    /**
     * 运送类型:1-普快,2-特快
     */
    private Integer transportType;

    /**
     * 关联城市:1-全国,2-京津冀,3-江浙沪,4-川渝,5-黑吉辽
     */
    private String associatedCity;

    /**
     * 首重价格
     */
    private Double firstWeight;

    /**
     * 续重价格
     */
    private Double continuousWeight;

    /**
     * 轻抛系数
     */
    private Integer lightThrowingCoefficient;
}

 BaseEntity类

@Data
public abstract class BaseEntity implements Serializable {

    @TableId
    private Long id; //主键id

    @TableField(fill = FieldFill.INSERT) //MP自动填充
    
    private LocalDateTime created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
   
    private LocalDateTime updated;
}

查询运费模板服务

/**
 * 获取全部运费模板,按照创建时间倒序排序
 *
 * @return 运费模板对象列表
 */

这里是通过工具类批量把entity类对象转换成dto对象,然后返回给前端

    @Override
    public List<CarriageDTO> findAll() {
        // 构造查询条件,按创建时间倒序
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery().orderByDesc(CarriageEntity::getCreated);
        // 查询数据库
        List<CarriageEntity> carriageEntities = baseMapper.selectList(wrapper);
        // 将结果转换为DTO类型  使用CarriageUtils工具类
        return carriageEntities.stream().map(CarriageUtils::toDTO).collect(Collectors.toList());
    }

新增和修改运费模块

整体流程

流程说明:

  • 根据传入的CarriageDTO对象参数进行查询模板,判断模板是否存在,如果不存在直接落库
  • 如果存在,需要进一步的判断是否为经济区互寄,如果不是,说明模板重复,不能落库
  • 如果是经济区互寄,再进一步的判断是否有重复的城市,如果是,模板重复,不能落库
  • 如果不重复,落库,响应返回

模板为什么不能重复?

因为运费的计算是通过模板进行的,如果存在多个模板,该基于哪个模板计算呢?所以模板是不能重复的。

代码实现

        // 校验运费模板是否存在,如果不存在直接插入 (查询条件: 模板类型  运输类型   如果是修改排除当前id)
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, carriageDto.getTemplateType())
                .eq(CarriageEntity::getTransportType, carriageDto.getTransportType())
                //如果有id字段值,就说明是更新操作,那么就需要把与自己id值一样的模板排除掉,不然会与自己模板类型一致冲突
                .ne(ObjectUtil.isNotEmpty(carriageDto.getId()), CarriageEntity::getId, carriageDto.getId());

这段代码构建的条件查询对象,特别注意ne()方法,如果dto对象的id成员变量不为null,说明就是更新操作,就说明在数据库中,有与这个对象一模一样的模板,所以需要排除掉这个存在与数据库中的模板(根据id主键值)

 // 如果是经济区互寄类型,需进一步判断关联城市是否重复,通过集合取交集判断是否重复
        List<String> assCities = carriageEntityList.stream().map(CarriageEntity::getAssociatedCity)//获得每一个数据关联的城市字符串
                .map(s -> {
                    return s.split(",");//将每一个字符串变成一个字符串数组
                })
                .flatMap(Arrays::stream)//将每一个字符串数组重新变成流对象
                //TODO:不然直接收集的是List<String[]>类型
                .collect(Collectors.toList());
        Collection<String> intersection = CollUtil.intersection(assCities, carriageDto.getAssociatedCityList());

在数据库中,关联城市字段是 2,3,4 使用,间隔的字符串类型,2是城市id。

所以需要把这个字符串分割成字符数组。 

.flatMap(Arrays::stream)

将每一个字符串数组重新变成流对象,不然直接收集的是List<String[]>类型,

assCities是数据表中所有的关联城市集合,最后判断与dto传入的关联城市集合是否有重复

    /**
     * 流程说明:
     * ● 根据传入的CarriageDTO对象参数进行查询模板,判断模板是否存在,如果不存在直接落库
     * ● 如果存在,需要进一步的判断是否为经济区互寄,如果不是,说明模板重复,不能落库
     * ● 如果是经济区互寄,再进一步的判断是否有重复的城市,如果是,模板重复,不能落库
     * ● 如果不重复,落库,响应返回
     * 模板为什么不能重复?
     * 因为运费的计算是通过模板进行的,如果存在多个模板,该基于哪个模板计算呢?所以模板是不能重复的。
     * @param carriageDto 新增/修改运费对象
     * @return
     *
     * 模板类型常量 {@link com.sl.ms.carriage.domain.constant.CarriageConstant}
     *
     */
    @Override
    public CarriageDTO saveOrUpdate(CarriageDTO carriageDto) {
        // 校验运费模板是否存在,如果不存在直接插入 (查询条件: 模板类型  运输类型   如果是修改排除当前id)
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, carriageDto.getTemplateType())
                .eq(CarriageEntity::getTransportType, carriageDto.getTransportType())
                //如果有id字段值,就说明是更新操作,那么就需要把与自己id值一样的模板排除掉,不然会与自己模板类型一致冲突
                .ne(ObjectUtil.isNotEmpty(carriageDto.getId()), CarriageEntity::getId, carriageDto.getId());
        List<CarriageEntity> carriageEntityList = super.list(wrapper);
        // 如果没有重复的模板,可以直接插入或更新操作 (DTO 转 entity 保存成功 entity 转 DTO)
        if (CollUtil.isEmpty(carriageEntityList)){
            return saveOrUpdateCarriage(carriageDto);
        }
        // 如果存在重复模板,需要判断此次插入的是否为经济区互寄,非经济区互寄是不可以重复的
        if(ObjectUtil.notEqual(carriageDto.getTemplateType(),CarriageConstant.ECONOMIC_ZONE)){
            throw new SLException(CarriageExceptionEnum.NOT_ECONOMIC_ZONE_REPEAT);
        }
        // 如果是经济区互寄类型,需进一步判断关联城市是否重复,通过集合取交集判断是否重复
        List<String> assCities = carriageEntityList.stream().map(CarriageEntity::getAssociatedCity)//获得每一个数据关联的城市字符串
                .map(s -> {
                    return s.split(",");//将每一个字符串变成一个字符串数组
                })
                .flatMap(Arrays::stream)//将每一个字符串数组重新变成流对象
                //TODO:不然直接收集的是List<String[]>类型
                .collect(Collectors.toList());
        Collection<String> intersection = CollUtil.intersection(assCities, carriageDto.getAssociatedCityList());
        // 如果没有重复,可以新增或更新 (DTO 转 entity 保存成功 entity 转 DTO)
        if(CollUtil.isEmpty(intersection)){
            return saveOrUpdateCarriage(carriageDto);
        }else{
            throw new SLException(CarriageExceptionEnum.ECONOMIC_ZONE_CITY_REPEAT);
        }

    }
    public CarriageDTO saveOrUpdateCarriage(CarriageDTO carriageDTO){
        CarriageEntity entity = CarriageUtils.toEntity(carriageDTO);
        super.saveOrUpdate(entity);
        return CarriageUtils.toDTO(entity);
    }

运费计算 

整体流程

说明:

  • 运费模板优先级:同城>省内>经济区互寄>跨省
  • 将体积转化成重量,与重量比较,取大值

总的代码 

/**
     * ● 运费模板优先级:同城>省内>经济区互寄>跨省
     * ● 将体积转化成重量,与重量比较,取大值
     * @param waybillDTO 运费计算对象
     * @return
     *
     *  
     */
  
    @Override
    public CarriageDTO compute(WaybillDTO waybillDTO) {
        // 根据参数查找运费模板 调用findCarriage方法
        CarriageEntity carriage = findCarriage(waybillDTO);
        // 计算重量,最小重量为1kg 调用getComputeWeight方法
        double weight = getComputeWeight(waybillDTO, carriage);
        // 计算运费  运费 = 首重价格 + (实际重量 - 1) * 续重架构
        double money=carriage.getFirstWeight()+(weight-1)*carriage.getContinuousWeight();
        // 结果四舍五入保留一位小数
        money=NumberUtil.round(money,1).doubleValue();
        // 封装运费和计算重量到 CarriageDTO,并返回
        CarriageDTO carriageDTO = CarriageUtils.toDTO(carriage);
        carriageDTO.setComputeWeight(weight);
        carriageDTO.setExpense(money);
        return carriageDTO;
    }
 查找运费模板方法
/**
     * 根据参数查找运费模板
     * 运费模板优先级:同城>省内>经济区互寄>跨省
     * @param waybillDTO 参数
     * @return 运费模板
     */
    @Resource
    private AreaFeign areaFeign;
    private CarriageEntity findCarriage(WaybillDTO waybillDTO) {
        // 如果 发件的城市id 和 收件的城市id相同, 查询同城模板 调用findByTemplateType方法
        if(ObjectUtil.equal(waybillDTO.getSenderCityId(),waybillDTO.getReceiverCityId())){
            CarriageEntity carriageEntity = findByTemplateType(CarriageConstant.SAME_CITY);
            if(ObjectUtil.isNotEmpty(carriageEntity)){
                return carriageEntity;
            }
        }
        // 如果没查到或不是同城,则获取收寄件地址省份id  使用AreaFeign结构查询
        //根据areaFeign接口获取这个市所属的省id
        Long receiveProvinceId = areaFeign.get(waybillDTO.getReceiverCityId()).getParentId();
        Long senderProvinceId = areaFeign.get(waybillDTO.getSenderCityId()).getParentId();
        // 如果 收发件的省份id相同,查询同省的模板  调用findByTemplateType方法
        if(ObjectUtil.equal(receiveProvinceId,senderProvinceId)){
            CarriageEntity carriageEntity = findByTemplateType(CarriageConstant.SAME_PROVINCE);
            if(ObjectUtil.isNotEmpty(carriageEntity)){
                return carriageEntity;
            }
        }
        // 如果没查到或不是同省,则查询是否为经济区互寄  调用findEconomicCarriage方法查询
        CarriageEntity carriageEntity = findEconomicCarriage(receiveProvinceId,senderProvinceId);
        if(ObjectUtil.isNotEmpty(carriageEntity)){
            return carriageEntity;
        }
        // 如果没查到或不是经济区互寄,直接查跨省运费模板
        carriageEntity = findByTemplateType(CarriageConstant.TRANS_PROVINCE);
        if(ObjectUtil.isNotEmpty(carriageEntity)){
            return carriageEntity;
        }
        // 如果最后没查到,直接抛自定义异常,提示模板未找到
        if(ObjectUtil.isEmpty(carriageEntity)){
            throw new SLException(CarriageExceptionEnum.NOT_FOUND);
        }
        return null;
    }
/**
     * @param receiverProvinceId 收件省份id
     * @param senderProvinceId 发件省份id
     * @return
     */
    private CarriageEntity findEconomicCarriage(Long receiverProvinceId, Long senderProvinceId) {
        //通过工具类EnumUtil 获取经济区城市配置枚举
        LinkedHashMap<String, EconomicRegionEnum> enumMap = EnumUtil.getEnumMap(EconomicRegionEnum.class);
        // 遍历所有经济区枚举值
        EconomicRegionEnum targetEconomicEnum=null;
        for (EconomicRegionEnum regionEnum : enumMap.values()) {
            //     通过ArrayUtil工具类 判断发件网点 和 收件网点是否在同一经济区
            if(ArrayUtil.containsAll(regionEnum.getValue(),receiverProvinceId,senderProvinceId)){
                //     如果在得到对应经济区枚举
                targetEconomicEnum=regionEnum;
                break;
            }
        }
        // 循环遍历未发现所属经济区,方法直接返回null
        if(ObjectUtil.isEmpty(targetEconomicEnum)){
            return null;
        }
        // 如果有经济区 根据 模板类型=经济区, 运输类型=普快  关联城市=枚举的code值 查询
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, CarriageConstant.ECONOMIC_ZONE)
                .eq(CarriageEntity::getTransportType, CarriageConstant.REGULAR_FAST)
                .like(CarriageEntity::getAssociatedCity, targetEconomicEnum.getCode());
        return super.getOne(wrapper);
    }
    /**
     * 根据模板类型查询模板
     * @param templateType 模板类型:1-同城寄,2-省内寄,3-经济区互寄,4-跨省
     * @return 运费模板
     */
    private CarriageEntity findByTemplateType(Integer templateType) {
        // 根据模板类型,及运输类型 = CarriageConstant.REGULAR_FAST 查询模板
        LambdaQueryWrapper<CarriageEntity> wrapper = Wrappers.<CarriageEntity>lambdaQuery()
                .eq(CarriageEntity::getTemplateType, templateType)
                .eq(CarriageEntity::getTransportType,CarriageConstant.REGULAR_FAST);
        return super.getOne(wrapper);
    }
经济特区枚举类
package com.sl.ms.carriage.domain.enums;

/**
 * 经济区枚举
 */
public enum EconomicRegionEnum {

    BTH("2", new Long[] {1L, 7362L, 13267L}),
    JZS("3", new Long[] {167904L, 191019L, 161792L}),
    SC("4", new Long[] {545532L, 533328L}),
    HJL("5", new Long[] {145665L, 133208L, 115224L});

    /**
     * 类型编码
     */
    private final String code;

    /**
     * 类型值
     */
    private final Long[] value;

    EconomicRegionEnum(String code, Long[] value) {
        this.code = code;
        this.value = value;
    }

    public String getCode() {
        return code;
    }

    public Long[] getValue() {
        return value;
    }

    public static EconomicRegionEnum codeOf(String code) {
        switch (code) {
            case "2": {
                return BTH;
            }
            case "3": {
                return JZS;
            }
            case "4": {
                return SC;
            }
            case "5": {
                return HJL;
            }
            default: {
                return null;
            }
        }
    }
}
计算重量方法
/**
     * 根据体积参数与实际重量计算计费重量
     *
     * @param waybillDTO 运费计算对象
     * @param carriage   运费模板
     * @return 计费重量
     */
    private double getComputeWeight(WaybillDTO waybillDTO, CarriageEntity carriage) {
        // 计算体积,如果传入体积则不需要计算
        Integer volume = waybillDTO.getVolume();
        if(ObjectUtil.isEmpty(volume)){
            try {
                volume=waybillDTO.getMeasureHigh()*waybillDTO.getMeasureLong()*waybillDTO.getMeasureWidth();
            } catch (Exception e) {//防止有null值
                throw new RuntimeException(e);
            }
        }
        // 计算体积重量  = 体积 / 轻抛系数  tips: 使用NumberUtil工具类计算 保留一位小数
        BigDecimal weightByV = NumberUtil.div(volume, carriage.getLightThrowingCoefficient(), 1);
        // 重量取大值 = 体积重量 和 实际重量 tips: 使用NumberUtil工具类计算   保留一位小数
        double weight = NumberUtil.max(weightByV.doubleValue(), NumberUtil.round(waybillDTO.getWeight(), 1).doubleValue());
        // 计算续重,规则:不满1kg,按1kg计费;
        if(weight<=1){
            return 1;
        }
        // 10kg以下续重以0.1kg计量保留1位小数;
        if(weight<=10){
            return weight;
        }
        // 100kg以上四舍五入取整  举例:108.4kg按照108kg收费 108.5kg按照109kg收费
        // tips: 使用NumberUtil工具类计算
        if(weight>=100){
            return NumberUtil.round(weight,0).doubleValue();
        }
        // 10-100kg续重以0.5kg计量保留1位小数;
        // 0.5为一个计算单位,举例:18.8kg按照19收费, 18.4kg按照18.5kg收费 18.1kg按照18.5kg收费
        int i=NumberUtil.round(weight, 0, RoundingMode.DOWN).intValue();//向下取整
        if(NumberUtil.sub(weight,i)==0){
            return weight;
        }else if(NumberUtil.sub(weight,i)<=0.5){
            return NumberUtil.add(i,0.5);
        }else{
            return NumberUtil.add(i,1);
        }
    }

Redis存入热点数据

思考:根据模板类型查询模板时需要频繁的查询数据库,性能较低,而且模板数据不会频繁变化,查询次数多,属于热点数据

所以可以使用redis存储模板entity类数据,

使用hash结构。大key->template 小key->发件城市id_收件城市

1.从nacos导入共享redis配置

spring:
  cloud:
    nacos:
      username: nacos
      password: nacos
      server-addr: 192.168.150.101:8848
      discovery:
        namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
        ip: 192.168.150.1
      config:
        namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
        shared-configs: #共享配置
          - data-id: shared-spring-seata.yml
            group: SHARED_GROUP
            refresh: false
          - data-id: shared-spring-mysql.yml
            group: SHARED_GROUP
            refresh: false
          - data-id: shared-spring-mybatis-plus.yml
            group: SHARED_GROUP
            refresh: false
#            导入redis共享配置
          - data-id: shared-spring-redis.yml
            group: SHARED_GROUP
            refresh: false

这是共享redis配置文件的内容,可以发现里面的值并没有进行定义,那在哪定义了呢

其实共享配置文件是定义了文件的结构,具体的内容由微服务的配置文件自己配置 

具体的配置在sl-express-ms-carriage.properties运费微服务自己的配置文件当中定义

 

2.自定义RedisTemplate,自定义序列化和反序列化方式

存入value值时使用json序列

@Configuration//告诉Spring这是一个配置类,并让Spring加载这个类
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
        //默认的key序列化器为:JdkSerializationRedisSerializer
        //我们改成StringRedisSerializer
        //键序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //值序列化
        // 创建一个json的序列化方式
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置value用jackjson进行处理
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //key hashMap序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //value hashMap序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        //连接工厂
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

}

 3.修改代码

    @Resource
    private RedisTemplate redisTemplate;
    @Override
    public CarriageDTO compute(WaybillDTO waybillDTO) {
        //TODO day02 计算运费
        HashOperations operations = redisTemplate.opsForHash();

        // 根据参数查找运费模板 调用findCarriage方法
        //CarriageEntity carriage = findCarriage(waybillDTO);
        /*
         * 根据模板类型查询模板时需要频繁的查询数据库,性能较低,而且模板数据不会频繁变化,查询次数多,属于热点数据
         * 可以存于redis中,使用hash结构。大key->template 小key->发件城市id_收件城市
         */
        //组装小key
        String sKey = String.join("_", waybillDTO.getSenderCityId() + "", waybillDTO.getReceiverCityId() + "");
        //先从redis中查数据
        CarriageEntity carriage= (CarriageEntity) operations.get(RedisConstant.templateCode, sKey);
        if(ObjectUtil.isEmpty(carriage)){
            //redis没有改数据,就从数据库中查询
            carriage=findCarriage(waybillDTO);
            if(ObjectUtil.isEmpty(carriage)){
                return null;
            }
            //将数据存入redis
            operations.put(RedisConstant.templateCode,sKey,carriage);
        }
        // 计算重量,最小重量为1kg 调用getComputeWeight方法
        double weight = getComputeWeight(waybillDTO, carriage);
        // 计算运费  运费 = 首重价格 + (实际重量 - 1) * 续重架构
        double money=carriage.getFirstWeight()+(weight-1)*carriage.getContinuousWeight();
        // 结果四舍五入保留一位小数
        money=NumberUtil.round(money,1).doubleValue();
        // 封装运费和计算重量到 CarriageDTO,并返回
        CarriageDTO carriageDTO = CarriageUtils.toDTO(carriage);
        carriageDTO.setComputeWeight(weight);
        carriageDTO.setExpense(money);
        return carriageDTO;
    }

4.存入数据时报错

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module 
"com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through 
reference chain: java.util.ArrayList[0]->com.zym.entity.Banner["gmtCreate"])

解决:

导入依赖

<dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.12.3</version>
        </dependency>

给LocalTime数据类型的成员变量规定序列化方式和反序列化方式

@Data
public abstract class BaseEntity implements Serializable {

    @TableId
    private Long id; //主键id

    @TableField(fill = FieldFill.INSERT) //MP自动填充
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)		// 反序列化
    @JsonSerialize(using = LocalDateTimeSerializer.class)		// 序列化
    private LocalDateTime created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)		// 反序列化
    @JsonSerialize(using = LocalDateTimeSerializer.class)		// 序列化
    private LocalDateTime updated;
}

第二种代码方式

1.直接注入StringRedisTemplate模板类

这样一来就不用写配置类,重新定义RedisTemplate,它会把value序列化成String类型存入redis中

  @Resource
    private StringRedisTemplate stringRedisTemplate;

 2.存取数据

//获取hashKey
        String sKey = StrUtil.format("{}_{}", waybillDTO.getSenderCityId(), waybillDTO.getReceiverCityId());
        Object cacheData = stringRedisTemplate.opsForHash().get(RedisConstant.templateCode, sKey);
        CarriageEntity carriage=null;
        if(ObjectUtil.isNotEmpty(cacheData)){
//将json数据反序列化成实体类对象,先将cacheData变成String类型,再反序列化
            carriage= JSONUtil.toBean(StrUtil.toString(cacheData),CarriageEntity.class);
        }else{
            //从数据库中查
            carriage = this.findCarriage(waybillDTO);
            //将实体类转成json字符串存入redis中
            stringRedisTemplate.opsForHash().put(RedisConstant.templateCode,sKey,JSONUtil.toJsonStr(carriage));
        }

结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2256711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在windows10上部署WebDAV服务并通过内网穿透实现公网分享内部公共文件

WebDAV&#xff08;Web-based Distributed Authoring and Versioning&#xff09;是一种基于HTTP协议的应用层网络协议&#xff0c;它允许用户通过互联网进行文件的编辑和管理。这意味着&#xff0c;无论员工身处何地&#xff0c;只要连接到互联网&#xff0c;就能访问、编辑和…

gRPC 快速入门 — SpringBoot 实现(1)

目录 一、什么是 RPC 框架 &#xff1f; 二、什么是 gRPC 框架 &#xff1f; 三、传统 RPC 与 gRPC 对比 四、gRPC 的优势和适用场景 五、gRPC 在分布式系统中应用场景 六、什么是 Protocol Buffers&#xff08;ProtoBuf&#xff09;&#xff1f; 特点 使用场景 简单的…

深入浅出:SOME/IP-SD的工作原理与应用

目录 往期推荐 相关缩略语 SOME/IP 协议概述 协议介绍 SOME/IP TP 模块概述和 BSW 模块依赖性 原始 SOME/IP 消息的Header格式 SOME/IP-SD 模块概述 模块介绍 BSW modules依赖 客户端-服务器通信示例 Message 结构 用于SD服务的BSWM状态处理 往期推荐 ETAS工具…

字节高频算法面试题:小于 n 的最大数

问题描述&#xff08;感觉n的位数需要大于等于2&#xff0c;因为n的位数1的话会有点问题&#xff0c;“且无重复”是指nums中存在重复&#xff0c;但是最后返回的小于n最大数是可以重复使用nums中的元素的&#xff09;&#xff1a; 思路&#xff1a; 先对nums倒序排序 暴力回…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

在 Windows 11 WSL (Ubuntu 24.04.1 LTS) | Python 3.12.x 下部署密码学库 charm

1. 在 Windows 11 上部署 Ubuntu (WSL) 由于作者没有高性能的 Ubuntu 服务器或个人电脑&#xff0c;且公司或学校提供的 Ubuntu 服务器虽然提供高性能 GPU 等硬件配置但通常不会提供 root 权限&#xff0c;因而作者通过在搭载了 Windows 11 的个人电脑上启动 Ubuntu (WSL) 来进…

【中间件开发】Redis基础命令详解及概念介绍

文章目录 前言一、Redis相关命令详解及原理1.1 string、set、zset、list、hash1.1.1 string1.1.2 list1.1.3 hash1.1.4 set1.1.5 zset 1.2 分布式锁的实现1.3 lua脚本解决ACID原子性1.4 Redis事务的ACID性质分析 二、Redis协议与异步方式2.1 Redis协议解析2.1.1 redis pipeline…

设计模式的艺术读书笔记

设计模式的艺术 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 创建的艺术创建型模式单例模式饿汉式单例与懒汉式单例的讨论通过静态内部类实现的更好办法 简单工厂模式工厂方法模式重载的工厂方法工厂方法的隐藏工厂方…

计算机毕设-基于springboot的甜品店管理系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Mac 录制电脑系统内的声音的具体方法?

1.第一步&#xff1a;下载BlackHole 软件 方式1&#xff1a;BlackHole官方下载地址 方式2&#xff1a; 百度云下载 提取码: n5dp 2.第二步&#xff1a;安装BlackHole 双击下载好的BlackHole安装包&#xff0c;安装默认提示安装。 3.第三步&#xff1a;在应用程序中找到音频…

【开源免费】基于Vue和SpringBoot的课程答疑系统(附论文)

博主说明&#xff1a;本文项目编号 T 070 &#xff0c;文末自助获取源码 \color{red}{T070&#xff0c;文末自助获取源码} T070&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

ACM latex模板中的CCSXML (即:CCS Concept)怎么填?

CCS Concept 感谢CCS Concept 怎么填 的珠玉在前. 问题描述 如下&#xff0c;ACM模板&#xff08;比如ACM computing surveys&#xff09;有一段是需要填写 ccsxml&#xff1a; %% %% The code below is generated by the tool at [http://dl.acm.org/ccs.cfm.](http://dl.…

【Transformer序列预测】Pytorch中构建Transformer对序列进行预测源代码

Python&#xff0c;Pytorch中构建Transformer进行序列预测源程序。包含所有的源代码和数据&#xff0c;程序能够一键运行。此程序是完整的Transformer&#xff0c;即使用了Encoder、Decoder和Embedding所有模块。源程序是用jupyterLab所写&#xff0c;建议分块运行。也整理了.p…

Mybatis-plus 简单使用,mybatis-plus 分页模糊查询报500 的错

一、mybtis-plus配置下载 MyBatis-Plus 是一个 Mybatis 增强版工具&#xff0c;在 MyBatis 上扩充了其他功能没有改变其基本功能&#xff0c;为了简化开发提交效率而存在。 具体的介绍请参见官方文档。 官网文档地址&#xff1a;mybatis-plus 添加mybatis-plus依赖 <depe…

前端项目使用gitlab-cicd+docker实现自动化部署

GitLab CI/CD 是一个强大的工具&#xff0c;可以实现项目的自动化部署流程&#xff0c;从代码提交到部署只需几个步骤。本文将带你配置 GitLab CI/CD 完成一个前端项目的自动化部署。 前言 为什么使用cicddocker&#xff1f; 目前我们公司开发环境使用的shell脚本部署&#…

设计模式:20、状态模式(状态对象)

目录 0、定义 1、状态模式的三种角色 2、状态模式的UML类图 3、示例代码 0、定义 允许一个对象在其内部状态改变时改变它的行为&#xff0c;对象看起来似乎修改了它的类。 1、状态模式的三种角色 环境&#xff08;Context&#xff09;&#xff1a;环境是一个类&#xff0…

Unity3D学习FPS游戏(13)玩家血量控制

玩家血量控制 血条UI玩家Canvas下的Slider血量逻辑控制 子弹攻击掉血子弹发射者的区分玩家受伤逻辑子弹碰撞检测 效果 血条UI 和之前我们前面介绍的玩家武器弹夹UI的思路是一样的&#xff0c;跟详细的细节可以参考博客Unity3D装弹和弹夹UI显示。 玩家Canvas下的Slider 之前玩…

【开源免费】基于SpringBoot+Vue.JS高校学科竞赛平台(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 075 &#xff0c;文末自助获取源码 \color{red}{T075&#xff0c;文末自助获取源码} T075&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

学在西电录播课使用python下载,通过解析m3u8协议、多线程下载ts视频块以及ffmpeg合并

本文涵盖的内容仅供个人学习使用&#xff0c;如果侵犯学校权利&#xff0c;麻烦联系我删除。 初衷 研究生必修选逃&#xff0c; 期末复习怕漏过重点题目&#xff0c;但是看学在西电的录播回放课一卡一卡的&#xff0c;于是想在空余时间一个个下载下来&#xff0c;然后到时候就…

基于php+mysql的旅游网站——记忆旅行 旅游分享 攻略分享 设计与实现 源码 配置 文档

旅游网站 1.项目描述2. 概述3.项目功能4.界面展示5.源码获取 1.项目描述 摘 要 随着互联网的不断发展&#xff0c;计算机网络逐渐普及到人们的生活&#xff0c;为人们带来了便捷。互联网的趋势扩大&#xff0c;运用到家家户户中。各行各业都在考虑利用互联网将自己的信息推广…