关于Spring Boot应用系统避免因为日切(日期切换)导致请求结果变更的一种解决方案

一、前言

在系统开发过程中,有些业务功能面临日切(日期切换)问题,比如结息跑批问题,在当前工作日临近24点的时候触发结息,实际交易时间我们预期的是当前时间,但是由于业务执行耗时,可能进行了日切,业务跑到下一个工作日了,这样业务如果采用下一个工作日的时间进行业务计算,可能会导致业务结果与预期不一致,有没有什么解决方案呢?

二、解决方案

在这里,我们可以采用一种解决方案,就是交易时间不采用系统时间,交易时间的获取从上游应用(可以从时间服务器获取)请求传递下来,下游应用在处理业务时,采用传递的交易时间,而不是直接使用系统时间来处理。

下面采用一个案例进行演示说明:比如我们有一个利息结算场景,需要对金额进行每天产生的利息进行结算,某些请求可能在临近24点的时候触发,此时,下游系统处理时可能发生日切,此时我们要保证业务结果是符合预期的,按照上述描述进行演示。

1. 搭建nacos注册中心

我们这里采用两个应用来进行模拟业务请求,需要用到注册中心,这里采用Nacos作为注册中心:Nacos下载地址

Nacos部署可以参考:nacos安装手册

2. 创建服务提供方应用

首先创建一个简单spring Boot应用,当做服务提供方。

  1. 导入相关依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
  2. 增加相关配置

    server:
      port: 7092
    spring:
      cloud:
        nacos:
          discovery:
            namespace: public
          server-addr: 127.0.0.1:8848
      application:
        name: provider
    
  3. 编写业务逻辑代码:

    import com.alibaba.nacos.api.utils.StringUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.temporal.ChronoUnit;
    import java.util.Map;
    
    @RestController
    public class ProviderController {
    
        /**
         * 计算利息
         *
         * @param paraMap
         * @return
         */
        // 以下逻辑只是为了业务说明,并非真实业务
        @PostMapping("/calInterest")
        public BigDecimal calInterest(@RequestBody Map<String, Object> paraMap) {
    
            // 以下逻辑只是为了业务说明,并非真实业务
    
            // 获取交易日期
            String traceDateStr = (String) paraMap.get("traceDate");
            // 如果有交易日期则取当前日期,否则取系统时间
            LocalDate traceDate = StringUtils.isBlank(traceDateStr) ? LocalDate.now() : LocalDate.parse(traceDateStr);
    
            // 获取存款日期
            LocalDate savaDate = LocalDate.parse((String) paraMap.get("savaDate"));
            // 获取间隔天数
            long days = ChronoUnit.DAYS.between(savaDate, traceDate);
            // 获取利率
            BigDecimal rate = new BigDecimal((String) paraMap.get("rate"));
            // 获取金额
            BigDecimal money = new BigDecimal((String) paraMap.get("money"));
    
            return money.add(money.multiply(rate).multiply(BigDecimal.valueOf(days)));
        }
    }
    

注意:如果应用启动报com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING异常,则可能是Nacos服务版本和应用里面引入的nacos-client版本不匹配,需要进行匹配对应。

3. 创建发起方应用

  1. 导入发起方所需依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
  2. 增加相关配置

    server:
      port: 7090
    spring:
      cloud:
        nacos:
          discovery:
            namespace: public
          server-addr: 127.0.0.1:8848
      application:
        name: consumer
    
    consumer:
      business:
        interval: 36000 # 业务处理与下一个工作日切换允许的时间间隔,超过这个时间,就需要传递交易时间,避免因业务在日切之前还未完成
        # 可以针对每个URL请求单独配置
        bm:
          - url: /calInterest
            interval: 18000
    feign:
      client:
        config:
          provider:  # 只针对provider这个应用生效,也可以配置全局生效
            request-interceptors:
              - com.learn.interceptor.CustomFeignInterceptor
    
  3. 声明远程feign调用

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.math.BigDecimal;
import java.util.Map;

@FeignClient(name = "provider")
public interface CalInterestService {

    @PostMapping("/calInterest")
    BigDecimal calInterest(@RequestBody Map<String, Object> paraMap);
}
  1. 增加自定义配置读取

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    @Component
    @ConfigurationProperties(prefix = "consumer.business")
    @Data
    public class BusinessDataPro {
    
        private long interval;
    
        private List<BusinessUrl> bm;
    
        @Data
        public static class BusinessUrl{
            private String url;
            private long interval;
        }
    
    }
    
  2. 增加feign拦截器,增加时间判断逻辑

    import com.alibaba.fastjson.JSON;
    import com.learn.pro.BusinessDataPro;
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import java.time.Duration;
    import java.time.LocalTime;
    import java.util.List;
    import java.util.Map;
    
    @Component
    public class CustomFeignInterceptor implements RequestInterceptor {
    
        @Autowired
        private BusinessDataPro businessDataPro;
    
        @Override
        public void apply(RequestTemplate template) {
            String url = template.url();
            List<BusinessDataPro.BusinessUrl> bm = businessDataPro.getBm();
            long interval = businessDataPro.getInterval();
            if (!CollectionUtils.isEmpty(bm)) {
                for (BusinessDataPro.BusinessUrl businessUrl : bm) {
                    if (businessUrl.getUrl().equals(url)) {
                        interval = businessUrl.getInterval();
                        break;
                    }
                }
            }
            byte[] body = template.body();
            Map<String, Object> paraMap = (Map<String, Object>) JSON.parse(body);
            LocalTime now = LocalTime.now(); // 获取当前时间
            LocalTime midnight = LocalTime.MIDNIGHT; // 午夜时间
            long time = Duration.between(midnight, now).toMillis(); // 获取时间差值
    
            if (time > interval) {
                // 如果日切时间大于配置的交易执行上限,就不需要传递交易时间,由下游应用自己获取本地时间
                paraMap.put("traceDate", null);
            }
            template.body(JSON.toJSONString(paraMap));
        }
    
    }
    
    1. 定义业务Controller逻辑

      import com.learn.controller.feign.CalInterestService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.math.BigDecimal;
      import java.time.LocalDate;
      import java.util.HashMap;
      import java.util.Map;
      
      @RestController
      public class DateChangeController {
      
          @Autowired
          private CalInterestService calInterestService;
      
      
          /**
           * 只是用于模拟业务操作,并非真实业务
           *
           * @param paraMap
           * @return
           */
          // 这里为了方便使用Map类型,实际业务开发中不能这么使用
          @PostMapping("/dcTest")
          public Map<String, Object> calInterest(@RequestBody Map<String, Object> paraMap) {
              String account = (String) paraMap.get("account");
              // 判断一些账户信息 start
              // 模拟业务
              try {
                  Thread.sleep(100L);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              // 判断一些账户信息 end
              paraMap.put("traceDate", LocalDate.now());
              BigDecimal bigDecimal = calInterestService.calInterest(paraMap);
      
              Map<String, Object> resultMap = new HashMap<>();
              resultMap.put("code", 200);
              resultMap.put("allCount", bigDecimal);
              return resultMap;
          }
      
      }
      

4. 测试

  1. 通过nacos查看应用注册是否正确

    在这里插入图片描述

  2. 请求测试

    在这里插入图片描述

三、最后

本文只是提供一种业务日切处理的大概思路,实际开发过程中,请以业务逻辑为根本,完善日切面临的问题解决方案,避免无脑照搬导致的业务异常。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【EI会议征稿通知】第五届城市工程与管理科学国际会议(ICUEMS 2024)

【Scopus稳定检索】第五届城市工程与管理科学国际会议&#xff08;ICUEMS 2024&#xff09; 2024 5th International Conference on Urban Engineering and Management Science 第五届城市工程与管理科学国际会议&#xff08;ICUEMS 2024&#xff09;将于2024年5月31日-6月2日…

告警能力中台设计与实践(三)——告警通知

一、告警消息与告警通知 1、告警消息 正如笔者在最开始所写的那样&#xff0c;第三方服务通过调用能力中台的OpenAPI实现告警发起&#xff0c;并且每一次的告警请求都会创建、归档为一条告警消息&#xff08;AlarmMsg&#xff09;。 这样的消息是无状态的&#xff0c;并且对…

Python:变量与数据类型

目录 一、变量 1.1 强数据类型与弱数据类型 1.2 全局函数 1.3 变量的命名规范 二、数据类型 2.1 基本数据类型 2.2 复合数据类型&#xff08;引用数据类型&#xff09; 三、数据类型转换 一、变量 变量&#xff1a;顾名思义&#xff0c;变化的量。在python中代指运行时…

【Java面试】MongoDB

目录 1、mongodb是什么&#xff1f;2、mongodb特点什么是NoSQL数据库&#xff1f;NoSQL和RDBMS有什么区别&#xff1f;在哪些情况下使用和不使用NoSQL数据库&#xff1f;NoSQL数据库有哪些类型?启用备份故障恢复需要多久什么是master或primary什么是secondary或slave系列文章版…

【Redis篇】详解布隆过滤器(原理 | 操作 | 代码)

文章目录 &#x1f354;简述布隆过滤器&#x1f33a;原理&#x1f6f8;存入过程&#x1f6f8;查询过程 &#x1f3f3;️‍&#x1f308;优缺点⭐优点⭐缺点 &#x1f339;代码实现&#xff08;本地&#xff09;&#x1f339;代码实现&#xff08;分布式&#xff09; &#x1f3…

Redis 集群(Cluster)

集群概念 Redis 的哨兵模式&#xff0c;提高了系统的可用性&#xff0c;但是正在用来存储数据的还是 master 和 slave 节点&#xff0c;所有的数据都需要存储在单个 master 和 salve 节点中。 如果数据量很大&#xff0c;接近超出了 master / slave 所在机器的物理内存&#…

HTTP请求报文与响应报文格式

HTTP请求报文与响应报文格式 HTTP请求报文与响应报文格式 请求报文包含四部分&#xff1a; a、请求行&#xff1a;包含请求方法、URI、HTTP版本信息b、请求首部字段c、请求内容实体d、空行 响应报文包含四部分&#xff1a; a、状态行&#xff1a;包含HTTP版本、状态码、状态码…

【从Python基础到深度学习】7. 使用scp命令实现主机间通讯

一、生成 SSH 密钥对 ssh-keygen 是一个用于生成 SSH 密钥对的命令行工具&#xff0c;用于身份验证和加密通信 ssh-keygen 二、将本地主机上的 SSH 公钥添加到远程主机 ssh-copy-id 命令用于将本地主机上的 SSH 公钥添加到远程主机上的 authorized_keys 文件中&#xff0c;…

《苍穹外卖》知识梳理P9-定时任务、来单提醒与用户催单

一.定时任务 实现定时任务可以使用spring家族中的sprinig-task&#xff1b; 1.1 spring-task spring-task是Spring框架的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑&#xff1b; 应用场景 信用卡每月归还贷款提醒&#xff0c;定时任务检查&#xff…

Jetpack Compose 第 2 课:布局

点击查看&#xff1a;Jetpack Compose 教程 点击查看&#xff1a;Composetutorial 代码 简介 Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API&#xff0c;可以帮助您简化并加快 Android 界面开发。 在本教程中&a…

【springboot+vue项目(十四)】基于Oauth2的SSO单点登录(一)整体流程介绍

场景&#xff1a;现在有一个前后端分离的系统&#xff0c;前端框架使用vue-element-template&#xff0c;后端框架使用springbootspringSecurityJWTRedis&#xff08;登录部分&#xff09;现在需要接入到已经存在的第三方基于oauth2.0的非标准接口统一认证系统。 温馨提示&…

html表格标签(下):lable标签,select标签和textara标签

html表格标签(下)&#xff1a;lable标签&#xff0c;select标签和textarea标签 lable标签 搭配 input 使用,点击 label 标签就能选中对应的单选/复选框, 能够提升用户体验。 for 属性: 指定当前 label 和哪个相同 id 的 input 标签对应 (此时点击才是有用的) 运行效果&#x…

php数组运算符 比较 isset、is_null、empty的用法和区别

php数组运算符 1. 数组运算符2. 判断两个数组是否相等3. isset、is_null、empty的用法和区别 1. 数组运算符 注意&#xff1a;只会保留第一个数组中的键值对&#xff0c;而忽略后面数组中相同键名的元素&#xff0c;如果想要合并两个数组并覆盖相同键名的元素&#xff0c;可以…

obsidian的Workbooks插件

学习目标&#xff1a; 学会使用obsidian 学习内容&#xff1a; obsidian咖啡豆教程 | Obsidian的Excel管理解密|Workbooks插件 直接在obsidian中插入表格编辑 但是在实际的使用过程中不好。虽然设置了自动保存&#xff0c;但是实际有时没有保存 读取外部excel文件进行修改 默…

【Jvm】性能调优(拓展)Jprofiler如何监控和解决死锁、内存泄露问题

文章目录 Jprofiler简介1.安装及IDEA集成Jprofiler2.如何监控并解决死锁3.如何监控及解决内存泄露(重点)4.总结5.后话 Jprofiler简介 Jprofilers是针对Java开发的性能分析工具(免费试用10天), 可以对Java程序的内存,CPU,线程,GC,锁等进行监控和分析, 1.安装及IDEA集成Jprofil…

VFH特征的使用(一)

一、SHOT特征描述符可视化 C #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d_omp.h> #include <pcl/registration/correspondence_estimation.h> #include <boo…

软件测试项目测试报告总结

测试计划概念&#xff1a;就在软件测试工作实施之前明确测试对象&#xff0c;并且通过资源、时间、风险、测试范围和预算等方面的综合分析和规划&#xff0c;保证有效的实施软件测试。 需求挖掘的6个方面&#xff1a; 1、输入方面 2、处理方面 3、结果输出方面 4、性能需求…

C语言学习day15:数组强化训练

题目一&#xff1a; 称体重&#xff1a;分别给10个值&#xff0c;来获得最大值 思路&#xff1a; 定义数组&#xff0c;给数组内赋10个值第一个下标的值与第二个下标的值进行比较定义max&#xff0c;将比较得来的较大的值赋值给max一直比较直到比较到最后一个下标&#xff0…

ubuntu22.04-磁盘管理-虚拟机动态扩容-系统monitor

文章目录 1.虚拟机2.ubuntu设置3.命令查看4.系统资源管理器1.虚拟机 关闭ubuntu22.04,然后修改虚拟机设置,如下图所示: 修改容量 2.ubuntu设置 搜索打开disks,如下图所示: 选择目标磁盘,选择调整大小到目标大小即可。

萝卜大杂烩 | 把微信接入ChatGPT,变成聊天机器人竟然这么简单!(一起来尝试吧~)

本文来源公众号“萝卜大杂烩”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;把微信接入ChatGPT&#xff0c;变成聊天机器人竟然这么简单&#xff01; 最近的 ChatGPT 又再次火热起来了&#xff0c;各种周边工具也是层出不穷&…
最新文章