Lottery 分布式抽奖(个人向记录总结)

news2024/11/15 11:59:25

1.搭建(DDD+RPC)架构

DDD——微服务架构(微服务是对系统拆分的方式)

(Domain-Driven Design 领域驱动设计)
DDD与MVC同属微服务架构
是由Eric Evans最先提出,目的是对软件所涉及到的领域进行建模,以应对系统规模过大时引起的软件复杂性的问题。

DDD分层

在这里插入图片描述

RPC接口:对外提供接口调用整个服务
DDD分层:(箭头是剪头尾对头的引用)

  • 接口层:对内提供接口【接口调用应用层,应用层调用领域层】
  • 基础层:对数据仓储服务【基础层引用领域层,领域层来定义仓储服务的接口,基础层去做仓储服务的实现】
  • 领域层:(核心)封装具体业务功能
  • 通用层:返回对象、枚举
  • 应用层:逻辑包装

MVC与DDD区别

在这里插入图片描述
驱动设计模式:

具体来说,对于这么一个抽奖领域domain
在这里插入图片描述

【上图repository包】这里只定义接口,在基础层infrastructure进行实现。
model,用于提供vo、req、res 和 aggregates 聚合对象
repository,对数据库,其实也就是对Mysql、Redis等数据的统一包装。
service,是具体的业务领域逻辑实现层,在这个包下定义了algorithm抽奖算法实现和具体的抽奖策略包装 draw 层,对外提供抽奖接口 IDrawExec#doDrawExec。

也就是说,这些model里的这些对象都是用于服务自己的领域,不会去服务其他领域

为什么使用斐波那契散列索引(感觉有点怪多余)

为了尽量均匀散列减少碰撞
使用斐波那契散列索引,使用斐波那契计算能起到不错的散列效果。

2.模版模式处理抽奖流程

职责分离,标准定义。
在这里插入图片描述

在这里插入图片描述
配置类:配置抽奖策略
抽象接口方法:【抽奖执行接口】
抽奖数据支撑:支撑类继承配置类里的方法,并提供数据服务
抽象类:提供标准的执行流程***【模板】(上面几个部分这么分就是为了将抽象类瘦身,将标准流程)
实现类:针对情景的具体
业务实现
*(将会在这@Service("drawExec")业务逻辑的实现层)

整个实现过程流式:这样将接口间的职责进行分离,将接口间的功能职责分离;

模板模式应用

本章节最大的目标在于把抽奖流程标准化,需要考虑的一条思路线包括:

  • 1根据入参策略ID获取抽奖策略配置
  • 2校验和处理抽奖策略的数据初始化到内存
  • 3获取那些被排除掉的抽奖列表,这些奖品可能是已经奖品库存为空,或者因为风控策略不能给这个用户薅羊毛的奖品
  • 4执行抽奖算法
  • 5包装中奖结果
    1,2是基础配置,3,4是需要根据自身具体业务实现——所以定义抽象类等具体业务去实现

模板方法定义好标准流程后,其他开发人员格子各自开发自己的业务,不会破坏整体的开发结构。
关于模版模式的核心点在于由抽象类定义抽象方法执行策略,也就是说父类规定了好一系列的执行标准,这些标准的串联成一整套业务流程
遇到适合的场景使用这样的设计模式也是非常方便的,因为他可以控制整套逻辑的执行顺序和统一的输入、输出,而对于实现方只需要关心好自己的业务逻辑即可

3.简单工厂搭建发奖domain

本质:就是为了简化if else判断不同类型使用不同的代码处理, 使用map将不同的类型和对应的代码联系到一起。让代码变得更整洁。

工厂模式:是一种创建型设计模式,在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
奖品服务交给工厂,工厂提供对外的发奖服务,由工厂进行统一包装【减少使用ifelse,使用map将奖品类型map过来】。
“发放奖品”工厂作用:外部提供一个奖品类型,工厂提供这个奖品类型需要提供什么样的服务去处理。

4.状态模式完成状态流转

状态模式(State Pattern):属于行为型模式,是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在软件开发中,经常需要根据对象的不同状态进行不同的逻辑处理,通常情况下我们会使用if-else/switch-case进行判断处理,但是大量的逻辑判断语句会使程序臃肿,不利用程序的扩展与维护。这时可以将不同状态下的逻辑处理抽象分离出来,使程序更加健壮。状态模式这批篇博客例子挺好
【这里也是用的map,将对应状态】

怎么使用的:
传一个状态实例,实现类里调用对应状态的要进行的操作。

5.策略模式选择生成ID算法

策略模式属于行为模式的一种,一个类的行为或算法可以在运行时进行更改。
使用策略模式把三种生成ID的算法进行统一包装,由调用方根据不同的场景来选择出适合的ID生成策略
这里三种方式生成ID雪花算法、随机算法、日期算法,分别用在订单号、策略ID、活动号的生成上。【使用map】
在这里插入图片描述

优惠策略的选取也是使用策略模式详见 实战策略模式「模拟多种营销类型优惠券,折扣金额计算策略场景」

6.分库分表来缓解缓存压力

基于 HashMap 核心设计原理,使用哈希散列+扰动函数的方式,把数据散列到多个库表中的组件。
由于业务体量较大,数据增长较快,所以需要把用户数据拆分到不同的库表中去,减轻数据库压力。
分库分表操作主要有垂直拆分和水平拆分:

  • 垂直拆分:指按照业务将表进行分类,分布到不同的数据库上,这样也就将数据的压力分担到不同的库上面。最终一个数据库由很多表的构成,每个表对应着不同的业务,也就是专库专用。
  • 水平拆分:如果垂直拆分后遇到单机瓶颈,可以使用水平拆分,区别是:垂直拆分是把不同的表拆到不同的数据库中,而本章节需要实现的水平拆分,是把同一个表拆到不同的数据库中。如:user_001、user_002

这里实现的是水平拆分的路由设计
大致思路:
分库:通过AOP方式,拦截@dbRouter注解,(将用户ID、订单ID)通过一致性Hash计算目标数据源,缓存到Threadlocal里。配置DynamicDataSource,从Threadlocal中读取目标数据源key执行切换。
分表:利用MyBatis拦截器,在@DBRouterStrategy(true)标记的类里,从Threadlocal中读取目标ID,补全SQL语句。
计算落到那个库中然后动态切换数据源
计算落到那个表中然后mybatis拦截器,拦截到对应的sql语句然后添加上具体的表单号。
在这里插入图片描述

只分库部分表只加@DBRouter路由,就到对应的
既分库又分表需要再@Mapper下加@DBRouterStrategy(splitTable = true)

(1)数据库路由设计要包括哪些技术知识点

  • AOP 切面拦截的使用:这是因为需要给使用数据库路由的方法做上标记,便于处理分库分表逻辑。
  • 数据源的切换操作:既然有分库那么就会涉及在多个数据源间进行链接切换,以便把数据分配给不同的数据库
  • 数据库表寻址操作:一条数据分配到哪个数据库,哪张表,都需要进行索引计算。在方法调用的过程中最终通过 ThreadLocal 记录
  • 数据散列的操作:让数据均匀的分配到不同的库表中去

(2)为什么分库分表?

分库分表基本是单表200万,才分,你们为什么分库分表?

  1. 我们分库分表用的非常熟。但不能为了等到系统到了200万数据,才拆。那么工作量会非常大
  2. 我们的做法是,因为有成熟方案,所以前期就分库分表了。但,为了解释服务器空间。所以把分库分表的库,用服务器虚拟出来机器安装。这样即不过多的占用服务器资源,也方便后续数据量真的上来了,好拆分。
  3. 同时,抽奖系统,是瞬时峰值较高的系统,历史数据不一定多。所以我们希望,用户可以**快速的检索到个人数据,做最优响应。**因为大家都知道,抽奖这东西,push发完,基本就1~3分钟结束,10分钟人都没了。所以我们这也是做了分库分表的理由。

(3)库表数量设置为什么是2的n次幂

算法基于HashMap,分表数量也要基于HashMap从而更好的散列,避免id字段都分配到一个库表上去不均匀。

在这里插入图片描述

7. 编程式事务领取活动开发

由于多次切换数据源导致使用注解式的事务失效,所以这里使用编程式事务,在开始之前将路由切换好。
好处:不用等抛出异常再事务回滚
扩展路由组件,拆解路由策略满足编程式路由配合编程式事务一起使用。
使用模板模式开发领取活动领域,因为在领取活动中需要进行活动的日期、库存、状态等校验,并处理扣减库存、添加用户领取信息、封装结果等一系列流程操作,因此使用抽象类定义模板模式更为妥当。
通过编程式事务将参与次数表的活动次数扣减写入用户领取活动表连在一起合并为一个事务。

每次写入参与活动时,会生成uuid用来防重,由uid(用户ID)+活动id+参与次数组成

8.应用层编排抽奖过程

领域层:写入一些功能
应用层:流程编排
在这里插入图片描述

(1)抽奖整个活动过程的流程编排,主要包括:对活动的领取、对抽奖的操作、对中奖结果的存放,以及如何处理发奖,MQ触发发奖流程。
对于每一个流程节点编排的内容,都是在领域层开发完成的,而应用层只是做最为简单的且很薄的一层。其实这块也很符合目前很多低代码的使用场景,通过界面可视化控制流程编排,生成代码。
(2)给user_take_activity表增加state【活动单使用状态 0未使用、1已使用】用于记录当前领取的活动有没有执行抽奖。目的是当抽奖过程中发生失败(系统,网络等原因),还触发到数据库中,这时用于保留未使用抽奖的状态。
同时,state还可以将两张表做一个幂等性的事务处理。
(幂等性问题就是同一个接口,多次发出同一个请求,必须保证操作只执行一次。)

(3)将user_take_activity表和 user_strategy_export_00(0-3) 表做一个幂等性的事务
用户领取活动表user_take_activity使用takeID生成user_strategy_export_表的UUID来防重,达到一次参加活动只生成一个抽奖单
UUID设置了unique唯一约束

抽奖单的UUID由领取活动表的takeid而来。

9.规则引擎,量化人群参与活动

使用组合模式搭建用于量化人群的规则引擎,用于用户参与活动之前,通过规则引擎过滤性别、年龄、首单消费、消费金额、忠实用户等各类身份来量化出具体可参与的抽奖活动。通过这样的方式控制运营成本和精细化运营
(1)增加规则引擎开发需要的相关的配置类表:rule_tree规则树(包括名字,描述等)'、rule_tree_node(节点类型,值,规则)、rule_tree_node_line(节点连接情况from,to)
(2)运用组合模式搭建规则引擎领域服务,包括:logic 逻辑过滤器、engine 引擎执行器
(3)修改 lottery-infrastructure 基础层中仓储实现类更为合适的的注解为 @Repository 包括: ActivityRepository、RuleRepository、StrategyRepository、UserTakeActivityRepository
(1)为什么使用组合模式?
组合模式是一种结构型模式,它将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
如果使用if-else语句去判断是哪种数据会比较麻烦且代码量大大增加,对以后的维护增加了难度,使用组合模式解决了这个问题,让代码更加干净整洁,为后续添加更多决策信息的时候更加轻便,且维护起来更加简单。
(2)这个规则树为啥要放到数据库里啊,直接写在代码里判断不行么?
放在数据库可以动态化配置,要的是这个。否则后面调整,只能改代码了。
决策树:
在这里插入图片描述
树结构原子模块的组织关系:开发每个节点(过滤器)的逻辑这每一个节点是用来作比对的
节点开发完后,执行引擎根据规则来串联节点关系得到决策树,最后遍历决策树得到过滤后的活动。

10.门面接口封装和对象转换

在领域层编写各种活动参与过程,抽奖过程,规则过程
在应用层进行逻辑包装
在接口层对外部提供应用
描述:在 lottery-interfaces 接口层创建 facade 门面模式 包装抽奖接口,并在 assembler 包 使用 MapStruct 做对象转换操作处理。

  • 补充 lottery-application 应用层对规则引擎的调用,添加接口方法 IActivityProcess#doRuleQuantificationCrowd
  • 删掉 lottery-rpc 测试内容,新增加抽奖活动展台接口 ILotteryActivityBooth,并添加两个抽奖的接口方法,普通抽奖和量化人群抽奖。
  • 开发 lottery-interfaces 接口层,对抽奖活动的封装,并对外提供抽奖服务。

对象转换
背景:以 DDD 设计的结构框架,在接口层和应用层需要做防污处理,也就是说不能直接把应用层、领域层的对象直接暴露处理,因为暴露出去可能会随着业务发展的过程中不断的添加各类字段,从而破坏领域结构。那么就需要增加一层对象转换,也就有了 vo2dto、dto2vo 的操作。但这些转换的字段又基本都是重复的,在保证性能的情况下,一些高并发场景就只会选择手动编写 get、set,但其实也有很多其他的方式,转换性能也不差,这里我们列举一下。

在这里插入图片描述
上图总结:BeanUtils.copyProperties 是大家代码里最常出现的工具类,但只要你不把它用错成 Apache 包下的,而是使用 Spring 提供的,就基本还不会对性能造成多大影响。
但如果说性能更好,**可替代手动get、set的,还是 MapStruct 更好用,**因为它本身就是在编译期生成get、set代码,和我们写get、set一样。

(1)MapStruct 对象转换操作

MapStruct使用:只需要定义一个 Mapper 接口,MapStruct就会自动实现这个映射接口,避免了复杂繁琐的映射实现。

//MapStruct 对象转换操作
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface AwardMapping extends IMapping<DrawAwardVO, AwardDTO> {

    @Mapping(target = "userId", source = "uId")
    @Override
    AwardDTO sourceToTarget(DrawAwardVO var1);

    @Override
    DrawAwardVO targetToSource(AwardDTO var1);

}
  1. 定义接口 AwardMapping 继承 IMapping<DrawAwardVO, AwardDTO> 做对象转换操作
  2. 如果一些接口字段在两个对象间不是同名的,则需要进行配置,就像 uId -> userId

11.MQ解耦抽奖发货流程

描述:使用MQ消息的特性,把用户抽奖到发货到流程进行解耦。这个过程中包括了消息的发送、库表中状态的更新、消息的接收消费、发奖状态的处理等。

  • Kafka:是一个高性能、可扩展的分布式发布订阅消息系统,主要用于处理大规模的实时数据流。它可以处理大量的数据,并使您能够将消息从一个端点传递到另一个端点。 Kafka适合离线和在线消息消费。 Kafka消息保留在磁盘上,并在群集内复制以防止数据丢失。

  • Kafka的Topic是一个消息的逻辑分类。一套消息系统中为多个模块(比如:订单模块和商品模块)提供服务。那就要对不同类型的消息进行逻辑分类,具体分类的方式就是用Topic进行区分,不同类别的消息具有不同的Topic。

  • Zookeeper:则是一个分布式协调服务,负责管理和协调分布式系统中的各种资源。
    Kafka构建在ZooKeeper同步服务之上。 它与Apache Storm和Spark非常好地集成,用于实时流式数据分析。

  • MQ消息队列:消息队列(Message Queue,简称MQ)指保存消息的一个容器,其实本质就是一个保存数据的队列。
    消息中间件是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的构建
    消息中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性的系统架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等。
    主要用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。以下介绍消息队列在实际应用中常用的使用场景:异步处理,应用解耦,流量削峰和消息通讯四个场景
    这里对MQ总结的挺好 全面了解消息队列MQ

  • 应用解耦:一般一个流程到另一个流程需要函数方法调用,而使用了MQ就可以不用直接调用而是通过订阅状态获取通知消息。

(1)用kafka+MQ处理发奖流程

MQ处理时需要考虑:(1)MQ发送失败需要Worker进行补偿发送(2)MQ消费(接收)失败,进行重试
这两次发送失败而发送多次,但对数据库的操作只能是一次,所以要进行幂等性操作。

  • 在数据库表 user_strategy_export 添加字段 mq_state 这个字段用于发送 MQ 成功更新库表状态,如果 MQ 消息发送失败则需要通过定时任务补偿 MQ 消息。
  • 启动 kafka 新增 topic:lottery_invoice 用于发货单消息,当抽奖完成后则发送一个发货单,再异步处理发货流程,这个部分就是MQ的解耦流程使用
    在这里插入图片描述

(1)生产消息:
lottery.application.mq.producer

@Component
public class KafkaProducer {

    private Logger logger = LoggerFactory.getLogger(KafkaProducer.class);

    @Resource
    private KafkaTemplate<String, Object> kafkaTemplate;

    /**
     * MQ主题:中奖发货单
     */
    public static final String TOPIC_INVOICE = "lottery_invoice";

    /**
     * 发送中奖物品发货单消息
     *
     * @param invoice 发货单
     */
    public ListenableFuture<SendResult<String, Object>> sendLotteryInvoice(InvoiceVO invoice) {
        String objJson = JSON.toJSONString(invoice);
        logger.info("发送MQ消息 topic:{} bizId:{} message:{}", TOPIC_INVOICE, invoice.getuId(), objJson);
        return kafkaTemplate.send(TOPIC_INVOICE, objJson);
    }
}

我们会把所有的生产消息都放到 KafkaProducer 中,并对外提供一个可以发送 MQ 消息的方法。
因为我们配置的类型转换为 StringDeserializer 所以发送消息的方式是 JSON 字符串,当然这个编解码器是可以重写的,满足你发送其他类型的数据。
(2)消费消息:
lottery.application.mq.consumer

@Component
public class LotteryInvoiceListener {

    private Logger logger = LoggerFactory.getLogger(LotteryInvoiceListener.class);

    @Resource
    private DistributionGoodsFactory distributionGoodsFactory;

    @KafkaListener(topics = "lottery_invoice", groupId = "lottery")
    public void onMessage(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
        Optional<?> message = Optional.ofNullable(record.value());

        // 1. 判断消息是否存在
        if (!message.isPresent()) {
            return;
        }

        // 2. 处理 MQ 消息
        try {
            // 1. 转化对象(或者你也可以重写Serializer<T>)
            InvoiceVO invoiceVO = JSON.parseObject((String) message.get(), InvoiceVO.class);

            // 2. 获取发送奖品工厂,执行发奖
            IDistributionGoods distributionGoodsService = distributionGoodsFactory.getDistributionGoodsService(invoiceVO.getAwardType());
            DistributionRes distributionRes = distributionGoodsService.doDistribution(new GoodsReq(invoiceVO.getuId(), invoiceVO.getOrderId(), invoiceVO.getAwardId(), invoiceVO.getAwardName(), invoiceVO.getAwardContent()));

            Assert.isTrue(Constants.AwardState.SUCCESS.getCode().equals(distributionRes.getCode()), distributionRes.getInfo());

            // 3. 打印日志
            logger.info("消费MQ消息,完成 topic:{} bizId:{} 发奖结果:{}", topic, invoiceVO.getuId(), JSON.toJSONString(distributionRes));

            // 4. 消息消费完成
            ack.acknowledge();
        } catch (Exception e) {
            // 发奖环节失败,消息重试。所有到环节,发货、更新库,都需要保证幂等。
            logger.error("消费MQ消息,失败 topic:{} message:{}", topic, message.get());
            throw e;
        }
    }
}

每一个 MQ 消息的消费都会有一个对应的 XxxListener 来处理消息体,如果你使用一些其他的 MQ 可能还会看到一些抽象类来处理 MQ 消息集合。
在这个 LotteryInvoiceListener 消息监听类中,主要就是通过消息中的发奖类型获取到对应的奖品发货工厂,处理奖品的发送操作。
在奖品发送操作中,已经补全了 DistributionBase# updateUserAwardState 更新奖品发送状态的操作。

(3)抽奖流程解耦:

 // 5. 发送MQ,触发发奖流程
        InvoiceVO invoiceVO = buildInvoiceVO(drawOrderVO);
        ListenableFuture<SendResult<String, Object>> future = kafkaProducer.sendLotteryInvoice(invoiceVO);//发送一个中奖结果的发货单
        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {//消息发送完毕后进行回调处理,更新数据库中 MQ 发送的状态

            @Override
            public void onSuccess(SendResult<String, Object> stringObjectSendResult) {
                // 4.1 MQ 消息发送完成,更新数据库表 user_strategy_export.mq_state = 1
                activityPartake.updateInvoiceMqState(invoiceVO.getuId(), invoiceVO.getOrderId(), Constants.MQState.COMPLETE.getCode());
            }

            @Override
            public void onFailure(Throwable throwable) {
                // 4.2 MQ 消息发送失败,更新数据库表 user_strategy_export.mq_state = 2 【等待定时任务扫码补偿MQ消息】
                activityPartake.updateInvoiceMqState(invoiceVO.getuId(), invoiceVO.getOrderId(), Constants.MQState.FAIL.getCode());
            }

        });

        // 6. 返回结果
        return new DrawProcessResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), drawAwardVO);

消息发送完毕后进行回调处理,更新数据库中 MQ 发送的状态,如果:场景-1:mq_state = 2 发送消息失败,定时任务扫描后直接触发发送,并更新发送状态。场景-2:mq_state = 1 且更新时间与现在时间对比超过15分钟或者10分钟,那么定时任务扫描触发发送MQ,并更新发送状态
现在从用户领取活动、执行抽奖、结果落库,到 发送MQ处理后续发奖的流程就解耦了,因为用户只需要知道自己中奖了,但发奖到货是可以等待的,毕竟发送虚拟商品的等待时间并不会很长,而实物商品走物流就更可以接收了。所以对于这样的流程进行解耦是非常有必要的,否则你的程序逻辑会让用户在界面等待更久的时间。

12.xxl job处理活动状态扫描

XXL-JOB 系统简介:
XXL-JOB是一个分布式任务调度平台,处理需要使用定时任务解决的场景。其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

任务需要在多台机器上跑

名词总结:

在代码发那个面这一整 个Project:叫做工程
业务:比如抽奖、添加购物车、删除购物车
事务:Spring的事务是逻辑上的一组操作,要么都执行,要么都不执行。
系统(应用):整个外卖平台、营销平台。有的时候也分开把每个微服务叫系统,比如前台系统、交易系统、支付系统等。
微服务:一套商城内,商品、下单、支付、发货、结算、营销(抽奖、优惠券)每一个是一套微服务,来构成整个商城。

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

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

相关文章

jenkins系列-05-jenkins构建golang程序

下载go1.20.2.linux-arm64.tar.gz 并存放到jenkins home目录&#xff1a; 写一个golang demo程序&#xff1a;静态文件服务器&#xff1a;https://gitee.com/jelex/jenkins_golang package mainimport ("encoding/base64""flag""fmt""lo…

搜索引擎中的相关性模型

一、什么是相关性模型&#xff1f; 相关性模型主要关注的是query和doc的相关性。例如给定query&#xff0c;和1000个doc&#xff0c;找到哪个doc是好query最相关的。 二、为什么需要相关性模型&#xff1f; 熟悉es的应该都熟悉BM25相关性算法。它是一个很简单的相关性算法。我…

【Linux】权限管理与相关指令

文章目录 1.权限、文件权限、用户文件权限的理解以及对应八进制数值表示、设置目录为粘滞位文件类型 2.权限相关的常用指令susudochmodchownchgrpumaskwhoamifile 1.权限、文件权限、用户 通过一定条件&#xff0c;拦住一部分人&#xff0c;给另一部分权利来访问资源&#xff0…

【node-RED 4.0.2】连接 Oracle 数据库踩坑解决,使用模组:node-red-contrib-agur-connector

关于 Oracle Oracle 就好像一张吸满水的面巾纸&#xff0c;你稍一用力它就烂了。 一、发现的问题 1.为什么需要 Oracle Instant Client && 不能使用 rpm 安装的原因 我们在使用 node-red 的 node-red-contrib-agur-connector 插件模组时&#xff0c;需要用到 Oracl…

QML界面控件加载与显示顺序

一、QML界面控件加载顺序 QML在界面加载时的顺序和我们认知的有很大的不同&#xff0c;有时候会对我们获取参数以及界面实现造成很大的困扰 1、加载顺序 import QtQuick 2.12 import QtQml 2.12 import QtQuick.Window 2.12 import QtQuick.VirtualKeyboard 2.4Window {id: …

Oracle使用fetch first子句报错:ORA-00933 SQL命令未正确结束

问题背景 今天在统计终端厂商告警次数Top10的时候使用SQL查询使用到了fetch first子句&#xff0c;结果执行报错&#xff1a;ORA-00933 SQL命令未正确结束。 报错原因 Oracle数据库中&#xff0c;使用 FETCH FIRST 子句需要启用 Oracle 12c 及以上版本。如果在较低版本的 Or…

德迅与DSV携香港蝴蝶效应集团,创半导体与新能源汽车物流新篇章

在全球经济一体化的大背景下,物流行业作为连接生产与消费的重要纽带,正迎来前所未有的发展机遇。特别是在半导体产业和新能源汽车领域,物流服务的专业性和高效性已成为企业竞争力的重要体现。近日,国际物流巨头德迅(Kuehne Nagel International)与全球汽车行业供应链物流专家D…

GitHub+Picgo图片上传

Picgo下载&#xff0c;修改安装路径&#xff0c;其他一路下一步&#xff01; 地址 注册GitHub&#xff0c;注册过程不详细展开&#xff0c;不会的百度一下 地址 新建GitHub仓库存放图片 生成Token令牌 点击头像&#xff0c;点击Settings 滑到最后 过期时间&#xff1a;No expi…

用HTML和CSS实现提示工具(tooltip)及HTML元素的定位

所谓提示工具&#xff0c;是指将鼠标移动到某个HTML元素&#xff08;工具&#xff09;时会显示一些提示内容&#xff08;提示文本&#xff09;&#xff0c;而鼠标移出工具元素的范围时提示文本就消失了。考虑到提示文本元素应当在鼠标进入工具元素时显示&#xff0c;鼠标离开工…

网络安全防御【防火墙NAT智能选举综合实验】

目录 一、实验拓扑图 二、实验要求 三、实验思路 四、实验步骤 1、FW2的网络相关配置&#xff1a; 2、路由器需要增加的&#xff08;接口&#xff09;命令配置 3、新增加的PC、client、sever的IP地址配置&#xff1a; 4、多对多的NAT&#xff0c;并且需要保留一个公网I…

LeetCode 3011.判断一个数组是否可以变为有序

注&#xff1a;这个题目有序的意思是“升序” 解法一&#xff1a;bubblesort O(nlogn) 核心思想&#xff1a;冒泡每次会将一个数归位到最后的位置上&#xff0c;所以我们如果碰到无法向右交换的数字&#xff0c;即可return false class Solution { public:// 返回一个十进制…

链接追踪系列-05.mac m1 安装es+kibana

运行启动脚本&#xff1a; docker run -e ES_JAVA_OPTS"-Xms512m -Xmx512m" -d -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" \-v /Users/jelex/dockerV/es/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml …

初涉项目架构

初涉项目架构 了解传统项目与互联网项目的区别 传统项目指OA、HR、CRM这种&#xff0c;互联网项目则是常见的app 首先是受众&#xff08;服务对象&#xff09;不同&#xff0c;传统项目是面向公司、学校等群体&#xff0c;互联网项目则是面向全体网民 两种对象数量不同&#x…

使用Java连接星火认知大模型:一个实际案例解析

引言&#xff1a; 随着人工智能技术的快速发展&#xff0c;认知大模型如星火在自然语言处理、语音识别等领域发挥着越来越重要的作用。本文将通过一个实际的Java代码示例&#xff0c;详细讲解如何使用Java连接星火认知大模型&#xff0c;并处理其响应。 1.导入依赖&#xff1…

Github 2024-07-13 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-07-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目3TypeScript项目2Go项目2Java项目2Rust项目1非开发语言项目1Solidity项目1从零开始构建你喜爱的技术 创建周期:2156 天Star数量:25…

如何在 Android Studio 中导出并在 IntelliJ IDEA 中查看应用的 SQLite 数据库

在 Android 应用开发过程中&#xff0c;调试和查看应用内的数据库内容是常见的需求。本文将介绍如何使用 Android Studio 导出应用的 SQLite 数据库&#xff0c;并在 IntelliJ IDEA 中查看该数据库。 步骤一&#xff1a;在设备上运行您的应用 首先&#xff0c;确保您的应用已…

T113-i系统启动速度优化方案

背景: 硬件:T113-i + emmc 软件:uboot2018 + linux5.4 + QT应用 分支:longan 问题: 全志T113-i的官方系统软件编译出的固件,开机启动时间10多秒,启动时间太长,远远超过行业内linux系统的开机速度,需要进一步优化。 T113-i 优化后启动速度实测数据 启动阶段启动时间(…

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models 0. 引言1. 快速开始2. 运行 Demo 0. 引言 EVE (Encoder-free Vision-language model) 是一种创新的多模态 AI 模型&#xff0c;主要特点是去除了传统视觉语言模型中的视觉编码器。 核心创新 架构创新&#xff…

装饰模式(大话设计模式)C/C++版本

装饰模式 需求分析&#xff1a; 1. 选择服饰 > 服饰类 2. 输出结果 对象是人 > 人类将Person类中一大堆服饰功能抽象出服饰类&#xff0c;然后通过Person类聚合服饰属性&#xff0c;通过Set行为来设置服饰属性&#xff0c;最后达到灵活打扮的效果 装饰模式 动态地给一个…

如何查找电脑的MAC地址

一. 什么是mac地址&#xff1f; mac地址本质上帮助我们连接到我们遇到的大多数本地网络。每个网络适配器通常由网络接口​​控制器(NIC) 制造商分配一个唯一的 mac 地址。 二. 如何查找mac地址 1.点击网络和Internet设置 2.点击WLAN点击硬件属性 3.即可查看mac地址