技术栈:SpringBoot + SpringSecurity + MyBatis-Plus + MySQL + Redis+ CouchDB + Hyperledger Fabric + RabbitMQ + Docker
项目描述:在自然灾害期间,物资管理常面临信息孤立和不透明等问题。为此,我们采用区块链技术开发了一个灾害救援物资管理系统。该系统通过区块链的分布式账本和智能合约确保物资信息真实、透明且不可篡改。通过实时追踪物资流向,提升了捐赠者和受援者之间的信任,优化了物资分配效率,为有效救援提供了坚实的技术支持。
职责描述: 主要负责登录认证、数据上链以及物资追踪模块的开发工作。
1.通过 SpringSecurity 和 JWT 双 token 刷新机制实现用户登录认证和授权。
2.实现物资从入库、运输到分发的整个生命周期管理。编写智能合约代码,打包成链码,安装并部署到区块链网络。通过Java SDK 调用链码, 实现在区块链上记录每一笔物资的流动。
3.通过 Hyperledger Fabric 技术在区块链网络中搭建 peer 节点、orderer节点的集群,实现身份和权限的校验,将交易列表排序并打包成区块,最后通过PBFT共识算法决定区块是否上链。
4.利用 Redis 缓存热点数据,优化了频繁的物资和用户信息查询,显著降低了数据库负载并提高了系统响应速度。
5.使用 RabbitMQ 异步处理数据上链请求,利用 RabbitMQ 的发布订阅模型,在物资入库、运输到达等关键流程节点自动触发更新通知。
6.客户端通过调用智能合约的查询接口来获取物资的溯源信息,并以列表的形式呈现物资的流通路径和历史记录。
用户首次登录时,我们将用户名密码封装到UsernamePasswordAuthenticationToken对象当中,其次需要实现UserDetailsService接口并重写loadUserByUsername根据用户名获取用户信息,最后我们会调用AuthenticationManager对象的authenticate方法并传入封装了用户名密码的UsernamePasswordAuthenticationToken对象,内部会比较前端传入的用户名密码和数据库查询的用户名密码是否一致。
如果一致就登陆成功,这个时候会将用户信息权限信息封装到一个LoginUser对象当中并缓存到Redis。同时根据用户id生成双token,access_token用于免登录的认证,refresh_token用于刷新过期时间,refresh_token缓存到redis。用户再次访问资源时会先携带access_token,拦截器会对请求进行拦截并判断access_token是否合法是否过期,过期的话就会通知前端,前端会携带refresh_token再次发送请求,refresh_token没有过期就会刷新双token的过期时间,采用的是先删除redis中refresh_token,然后根据JWT生成俩个全新的双token,将新的refresh_token缓存到redis并将新的双token响应给前端。
单token存在一些缺陷,过期时间设置太短比如30分钟,用户的体验就很差。过期时间设置太长,token就容易被盗取不安全。双token可以实现用户在access_token过期后,通过refresh_token来续期,用户体验好,同时刷新也保证了安全性(每次刷新会创建全新的token)。
区块链的不可篡改性是通过使用加密哈希函数、共识算法、去中心化的网络结构和区块确认机制来实现的。这些机制共同确保一旦数据被添加到区块链中,就无法被更改或删除。
区块链基本单位是区块,区块由区块头和区块体组成。
区块头保存了上一个区块的区块哈希,相当于指针;还保存了当前区块的一个merkle树的根哈希值,防止数据篡改就是通过这个merkle树来保证的。merkle树构建过程:区块体保存交易列表,我们计算每个交易数据的hash值并两两组合再次计算哈希值,最终就得到了merkle树的根hash值。后期如果我们需要获取该区块的交易信息,就会提前验证数据是否被篡改,验证方式就是对获取的区块交易列表重新构建merkle树得到一个根hash值,然后和原来的根hash进行对比,如果一致就代表没有被篡改,不一致就代表被篡改了。
哈希函数构造方法:除留取余法 直接定址法 平方取中法 数字分析法 折叠法
哈希冲突解决方法:链地址法 开放定址法(线性探测 平方探测) 再哈希法 建立公共溢出区
https://blog.csdn.net/weixin_46486402/article/details/139132744
总结:
通过fabric网络中的orderer节点的共识机制来保证。PBFT拜占庭,超过2/3组织数通过提案就广播并将新的区块加入到各个组织的本地区块链账本当中。
编写数据上链(增删改)操作 和 数据追踪(区块链区块历史记录查询) 的API接口,以及库存等判断。
用来存储本地区块链账本数据的副本,因为区块链是链表,查询效率低,CouchDB是一个分布式的文档型数据库, 区块链数据通常以JSON格式进行存储和交换,而CouchDB天生支持JSON文档的存储和查询。这使得CouchDB能够直接存储区块链数据而无需进行复杂的数据转换,简化了开发和集成的过程。CouchDB具有灵活的MapReduce查询引擎,可以对存储在其中的JSON文档进行复杂的查询和分析操作。这使得开发人员可以方便地进行区块链数据的查询和分析。
将CouchDB用于存储区块链副本而不是MySQL有几个原因:
综上所述,CouchDB作为一种分布式、灵活和高性能的文档型数据库,非常适合用于存储区块链副本。它提供了简单的部署和维护方式,同时具备强大的查询和扩展能力,能够满足区块链系统对于高可用性、可靠性和性能的要求。
实现物资从入库、运输到分发的整个生命周期管理。编写智能合约代码,打包成链码,安装并部署到区块链网络。通过Java SDK 调用链码, 实现在区块链上记录每一笔物资的流动,确保物资的追溯可靠性和供应链的透明度,以提高捐赠者和受赠者的信任度。
一个Fabric网络通常包含多个组织
每个组织有多个Peer节点,共享一个或多个Orderer节点来提供交易排序和区块打包服务。
peer节点又分为 背书节点 和 commit提交节点,其中背书节点用于提案(上链数据的操作)的身份验证 权限验证 以及交易的有效性,不同组织的背书策略不同;commit节点负责验证区块交易有效性,共识通过就将区块加入到该组织本地区块链上。
Orderer节点可以属于一个单独的Orderer组织,或由多个组织共享,不必每个组织都有。orderer节点用于对验证通过的交易列表进行排序并打包成区块,然后进入共识阶段,将区块广播给其他组织的peer的commit节点进行验证,只有有足够多的节点通过了这个提案达成共识,当前的orderer节点就会将这个提案通过的信息广播给所有组织,这样,所有组织(通过commit节点)都会将区块加入的各自本地的区块链账本上,保证了不同组织区块链账本数据的一致性。
Fabric搭建步骤:搭建流程
cryptogen
工具生成组织的MSP(身份校验 授权)和TLS(数据加密)材料。Orderer
和Peer
节点。Peer
节点加入通道。在一个物资管理系统中,假设有多个组织参与,每个组织都有自己的Peer节点和Orderer节点,彼此之间通过Hyperledger Fabric网络进行通信。以下是物资入库操作的整个过程:
客户端发起交易请求:
交易传递到客户端的Peer节点:
背书策略:
背书结果提交给客户端的Orderer节点:
Orderer节点进行排序和打包:
区块传递给各个组织的Peer节点:
共识过程:
提交到本地账本:
客户端可以通过调用区块链上的智能合约或者使用专门的查询接口来查询物资的溯源信息。这些接口可能会提供按照物资编号、生产日期、流通路径等条件进行查询,并返回相应的物资信息及其历史记录 交易的事务id。
数据可视化:获取到的物资溯源信息可以被客户端应用程序进行处理和展示。可以通过数据可视化的方式,以图表、列表等形式呈现物资的流通路径和历史记录,使用户能够清晰地了解物资的来源和去向。
溯源信息则更加全面和深入,它不仅包括流通路径的信息,还包括关于物资本身的详细数据,如生产批次、生产日期、成分或原料来源等。溯源信息的目的是提供足够的信息来追踪物资回到其原始来源,这在食品安全、药品监管和高价值商品中尤其重要。它有助于验证物资的真实性和质量,确保符合安全标准。
物资信息更新到区块链是一个资源密集型的过程,可能需要一些时间来完成。使用 RabbitMQ,您可以将数据上链的请求异步处理:
在物资管理和分发过程中,各个阶段完成后需要通知相关的利益相关者(如供应商、受赠者、监管机构等):
熟悉 RabbitMQ 的使用,有处理消息丢失、重复消费和消息顺序性问题,以及延迟队列的实现的实战经验。
主要从三个层面考虑:
第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据。
第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化。
第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理。
消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了
因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了。
其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的解决这个问题的。
延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。
如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。
我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤。
我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的
第一:提高消费者的消费能力 ,可以使用多线程消费任务
第二:增加更多消费者,提高消费速度
使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息
第三:扩大队列容积,提高堆积上限
可以使用RabbitMQ惰性队列,惰性队列的好处主要是
①接收到消息后直接存入磁盘而非内存
②消费者要消费消息时才会从磁盘中读取并加载到内存③支持数百万条的消息存储
我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失。
我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定
这个是仲裁队列即可。