当前位置: 首页 > news >正文

支付系统核心逻辑 — — 状态机(JavaGolang版本)

支付系统核心逻辑 — — 状态机

代码地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/state_machine_demo

1 概念:FSM(有限状态机),模式之间转换

状态机,也叫有限状态机(FSM,Finite State Machine),是一种行为模式,是由一组定义良好的状态、状态之间的转换规则和一个初始状态组成。

  • 根据当前的状态和输入的事件,从一个状态转移到另一个状态。

2 实战:支付核心逻辑

2.1 支付交易三重奏:收单、结算、拒付款

下图中我们可以看到,一共4种状态,每个状态之间的转换都通过指定事件触发。
在这里插入图片描述

2.2 状态机设计原则

无论是设计支付类的系统,还是电商类的系统,在设计状态机时,都建议遵循以下原则

  1. 明确性:状态和转换必须清晰定义,避免含糊不清的状态。
  2. 完备性:为所有可能的事件-状态组合定义转换逻辑。
  3. 可预测性:系统应根据当前状态和给定事件可预测地响应。
  4. 最小化:状态数应保持最小,避免不必要的复杂性。
①明确性:状态与转换必须定义清晰
②完备性:需要考虑所有事件-状态的转换组合
③可预测性:需根据当前状态+给定事件可预测响应
④最小化:状态数要少,避免过于复杂
常见误区
  1. 过度设计:引入不必要的状态
  2. 不完备的处理:没有考虑到状态与事件所有可能的转换关系,导致系统行为不确定
  3. 硬编码逻辑:过多硬编码转换逻辑,导致系统不具备可扩展性和灵活性

比如下面的设计:

一眼看过去,好像除了复杂一点,整体还是合理的,比如初始化,受理成功就到ACCEPT,然后到PAYING,如果直接成功就到PAIED,退款成功就到REFUND。

在这里插入图片描述

不合理的地方:

  1. 流程复杂。第一眼看过去会发现不那么清晰,流程比较繁琐,比较复杂,有很多状态都可以简化或者舍去。比如ACCEPT没有存在的必要。
  2. 职责不明确。支付单只管支付,到PAIED就算支付成功,最终状态不再改变。不应该后面还有REFUND状态。REFUND应该由退款单来负责处理,否则如果客户部分退款,我们就不好处理了。

改进方案:

  • 删除不必要的状态。如:ACCEPT
  • 将一个大型状态机抽取为多份小的状态机。比如把一些退款REFUND、请款等单据单独抽取出来。这个样子,虽然状态机数量多了,但是每个状态机都更加清晰明了。
  1. 主单:
    在这里插入图片描述
  1. 普通支付单
    在这里插入图片描述
  1. 预授权单
    在这里插入图片描述
  1. 请款单
    在这里插入图片描述
  1. 退款单
    在这里插入图片描述
最佳实践及代码规范

代码层面:

  1. 分离状态和处理逻辑:使用状态模式,将每个状态的行为都封装在各自的类中
  2. 使用事件驱动模型:通过事件来触发状态转换,而不是直接调用状态方法
  3. 确保可追踪性:状态转换应被记录和追踪,以便故障排查和审计

上面几点也就要求我们不应该使用if else或者switch case来写,会让代码看起来复杂。我们应该将每个状态封装为单独的类。

2.3 Java版本实现

  1. 定义状态基类
/*** 状态基类*/
public interface BaseStatus {
}
  1. 定义事件基类
/*** 事件基类*/
public interface BaseEvent {
}
  1. 定义状态-事件对,指定的状态只能接受指定的事件
/*** 状态事件对,指定的状态只能接受指定的事件*/
public class StatusEventPair<S extends BaseStatus, E extends BaseEvent> {/*** 指定的状态*/private final S status;/*** 可接受的事件*/private final E event;public StatusEventPair(S status, E event) {this.status = status;this.event = event;}@Overridepublic boolean equals(Object obj) {if (obj instanceof StatusEventPair) {StatusEventPair<S, E> other = (StatusEventPair<S, E>)obj;return this.status.equals(other.status) && this.event.equals(other.event);}return false;}@Overridepublic int hashCode() {// 这里使用的是google的guava包。com.google.common.base.Objectsreturn Objects.hashCode(status, event);}
}
  1. 定义状态机
/*** 状态机*/
public class StateMachine<S extends BaseStatus, E extends BaseEvent> {private final Map<StatusEventPair<S, E>, S> statusEventMap = new HashMap<>();/*** 只接受指定的当前状态下,指定的事件触发,可以到达的指定目标状态*/public void accept(S sourceStatus, E event, S targetStatus) {statusEventMap.put(new StatusEventPair<>(sourceStatus, event), targetStatus);}/*** 通过源状态和事件,获取目标状态*/public S getTargetStatus(S sourceStatus, E event) {return statusEventMap.get(new StatusEventPair<>(sourceStatus, event));}
}
  1. 定义支付状态机。注:支付、退款等不同的业务状态机是独立的。
/*** 支付状态机*/
public enum PaymentStatus implements BaseStatus {INIT("INIT", "初始化"),PAYING("PAYING", "支付中"),PAID("PAID", "支付成功"),FAILED("FAILED", "支付失败"),;// 支付状态机内容private static final StateMachine<PaymentStatus, PaymentEvent> STATE_MACHINE = new StateMachine<>();static {// 初始状态STATE_MACHINE.accept(null, PaymentEvent.PAY_CREATE, INIT);// 支付中STATE_MACHINE.accept(INIT, PaymentEvent.PAY_PROCESS, PAYING);// 支付成功STATE_MACHINE.accept(PAYING, PaymentEvent.PAY_SUCCESS, PAID);// 支付失败STATE_MACHINE.accept(PAYING, PaymentEvent.PAY_FAIL, FAILED);}// 状态private final String status;// 描述private final String description;PaymentStatus(String status, String description) {this.status = status;this.description = description;}/*** 通过源状态和事件类型获取目标状态*/public static PaymentStatus getTargetStatus(PaymentStatus sourceStatus, PaymentEvent event) {return STATE_MACHINE.getTargetStatus(sourceStatus, event);}
}
  1. 定义支付事件。注:支付、退款等不同业务的事件是不一样的。
/*** 支付事件*/
public enum PaymentEvent implements BaseEvent {// 支付创建PAY_CREATE("PAY_CREATE", "支付创建"),// 支付中PAY_PROCESS("PAY_PROCESS", "支付中"),// 支付成功PAY_SUCCESS("PAY_SUCCESS", "支付成功"),// 支付失败PAY_FAIL("PAY_FAIL", "支付失败");/*** 事件*/private String event;/*** 事件描述*/private String description;PaymentEvent(String event, String description) {this.event = event;this.description = description;}
}
  1. 在支付单模型中声明状态和根据事件推进状态的方法:
/*** 支付单模型*/
public class PaymentModel {/*** 其它所有字段省略*/// 上次状态private PaymentStatus lastStatus;// 当前状态private PaymentStatus currentStatus;/*** 根据事件推进状态*/public void transferStatusByEvent(PaymentEvent event) {// 根据当前状态和事件,去获取目标状态PaymentStatus targetStatus = PaymentStatus.getTargetStatus(currentStatus, event);// 如果目标状态不为空,说明是可以推进的if (targetStatus != null) {lastStatus = currentStatus;currentStatus = targetStatus;} else {// 目标状态为空,说明是非法推进,进入异常处理,这里只是抛出去,由调用者去具体处理throw new StateMachineException(currentStatus, event, "状态转换失败");}}
}

代码注释已经写得很清楚,其中StateMachineException是自定义,不想定义的话,直接使用RuntimeException也是可以的。

在支付业务代码中的使用:只需要paymentModel.transferStatusByEvent(PaymentEvent.valueOf(message.getEvent()))

/*** 支付领域域服务*/
public class PaymentDomainServiceImpl implements PaymentDomainService {/*** 支付结果通知*/public void notify(PaymentNotifyMessage message) {PaymentModel paymentModel = loadPaymentModel(message.getPaymentId());try {// 状态推进paymentModel.transferStatusByEvent(PaymentEvent.valueOf(message.getEvent()));savePaymentModel(paymentModel);// 其它业务处理... ...} catch (StateMachineException e) {// 异常处理... ...} catch (Exception e) {// 异常处理... ...}}
}

上面的代码只需要加完善异常处理,优化一下注释,就可以直接用起来。

上面写法的好处:

  1. 定义了明确的状态、事件。
  2. 状态机的推进,只能通过“当前状态、事件、目标状态”来推进,不能通过if else 或case switch来直接写。比如:STATE_MACHINE.accept(INIT, PaymentEvent.PAY_PROCESS, PAYING);
  3. 避免终态变更。比如线上碰到if else写状态机,渠道异步通知比同步返回还快,异步通知回来把订单更新为“PAIED”,然后同步返回的代码把单据重新推进到PAYING。

2.4 Golang版本实现

项目结构:
在这里插入图片描述

①定义基础状态机:base_state_machine.go
package modeltype BaseStatus interface {
}type BaseEvent interface {
}type StatusEventPair struct {status BaseStatusevent  BaseEvent
}func (pair StatusEventPair) equals(other StatusEventPair) bool {return pair.status == other.status && pair.event == other.event
}type StateMachine struct {statusEventMap map[StatusEventPair]BaseStatus
}func (sm *StateMachine) accept(sourceStatus BaseStatus, event BaseEvent, targetStatus BaseStatus) {pair := StatusEventPair{status: sourceStatus, event: event}sm.statusEventMap[pair] = targetStatus
}func (sm *StateMachine) getTargetStatus(sourceStatus BaseStatus, event BaseEvent) BaseStatus {pair := StatusEventPair{status: sourceStatus, event: event}baseStatus := sm.statusEventMap[pair]return baseStatus
}
②定义支付状态机:payment_state_machine.go
package modeltype PaymentStatus stringconst (INIT   PaymentStatus = "INIT"PAYING PaymentStatus = "PAYING"PAID   PaymentStatus = "PAID"FAILED PaymentStatus = "FAILED"
)type PaymentEvent stringconst (PAY_CREATE  PaymentEvent = "PAY_CREATE"PAY_PROCESS PaymentEvent = "PAY_PROCESS"PAY_SUCCESS PaymentEvent = "PAY_SUCCESS"PAY_FAIL    PaymentEvent = "PAY_FAIL"
)var PaymentStateMachine = StateMachine{statusEventMap: map[StatusEventPair]BaseStatus{}}func init() {//支付状态机初始化,包含所有可能的情况PaymentStateMachine.accept(nil, PAY_CREATE, INIT)PaymentStateMachine.accept(INIT, PAY_PROCESS, PAYING)PaymentStateMachine.accept(PAYING, PAY_SUCCESS, PAID)PaymentStateMachine.accept(PAYING, PAY_FAIL, FAILED)
}func GetTargetStatus(sourceStatus PaymentStatus, event PaymentEvent) PaymentStatus {status := PaymentStateMachine.getTargetStatus(sourceStatus, event)if status != nil {return status.(PaymentStatus)}panic("获取目标状态失败")
}type PaymentModel struct {lastStatus    PaymentStatusCurrentStatus PaymentStatus
}func (pm *PaymentModel) TransferStatusByEvent(event PaymentEvent) {targetStatus := GetTargetStatus(pm.CurrentStatus, event)if targetStatus != "" {pm.lastStatus = pm.CurrentStatuspm.CurrentStatus = targetStatus} else {// 处理异常panic("状态转换失败")}
}
③使用及测试

main.go:

package mainimport ("github.com/kataras/iris/v12""github.com/kataras/iris/v12/context""github.com/ziyifast/log""myTest/demo_home/state_machine_demo/model""time"
)var (testOrder = new(model.PaymentModel)
)func main() {application := iris.New()application.Get("/order/create", createOrder)application.Get("/order/pay", payOrder)application.Get("/order/status", getOrderStatus)application.Listen(":8899", nil)
}func createOrder(context *context.Context) {testOrder.CurrentStatus = model.INITcontext.WriteString("create order succ...")
}func payOrder(context *context.Context) {testOrder.TransferStatusByEvent(model.PAY_PROCESS)log.Infof("call third api....")//调用第三方支付接口和其他业务处理逻辑time.Sleep(time.Second * 15)log.Infof("done...")testOrder.TransferStatusByEvent(model.PAY_SUCCESS)
}func getOrderStatus(context *context.Context) {context.WriteString(string(testOrder.CurrentStatus))
}

声明:为了快速验证以及让代码更加简洁,没有按照标准的规范来编写controller、service、dao等。

测试:

  1. 启动程序,调用create接口,创建订单
http://localhost:8899/order/create

在这里插入图片描述

  1. 调用支付接口支付订单
http://localhost:8899/order/pay

我们手动模拟调用第三方支付接口,sleep了几十秒(实际调用肯定比这个快多了),所以不会立即返回结果,我们需要新开一个窗口,直接查询订单状态

在这里插入图片描述

  1. 立即调用查询接口获取订单状态,查看是否为支付中
http://localhost:8899/order/status

在这里插入图片描述

  1. 等待支付成功后,调用接口查看订单状态,是否为已支付

等待后台日志打印done之后重新调用查询接口:

在这里插入图片描述

http://localhost:8899/order/status

在这里插入图片描述

3 并发更新问题:多线程修改同一状态机(db版本号)

“状态机领域模型同时被两个线程操作怎么避免状态幂等问题?”
这是一个好问题。在分布式场景下,这种情况太过于常见。同一机器有可能多个线程处理同一笔业务,不同机器也可能处理同一笔业务。

业内通常的做法是设计良好的状态机 + 数据库锁 + 数据版本号解决。

在这里插入图片描述
简要说明:

  1. 状态机一定要设计好,只有特定的原始状态 + 特定的事件才可以推进到指定的状态。比如 INIT + 支付成功才能推进到sucess。
  2. 更新数据库之前,先使用select for update进行锁行记录,同时在更新时判断版本号是否是之前取出来的版本号,更新成功就结束,更新失败就组成消息发到消息队列,后面再消费。
  3. 通过补偿机制兜底,比如查询补单。

通过上述三个步骤,正常情况下,最终的数据状态一定是正确的。除非是某个系统有异常,比如外部渠道开始返回支付成功,然后又返回支付失败,说明依赖的外部系统已经异常,这样只能进人工差错处理流程。

参考文章:https://juejin.cn/post/7321569896453521419

相关文章:

支付系统核心逻辑 — — 状态机(JavaGolang版本)

支付系统核心逻辑 — — 状态机 代码地址&#xff1a;https://github.com/ziyifast/ziyifast-code_instruction/tree/main/state_machine_demo 1 概念&#xff1a;FSM&#xff08;有限状态机&#xff09;&#xff0c;模式之间转换 状态机&#xff0c;也叫有限状态机&#xff08…...

rest_framework_mongoengine实现后端的增删改查

rest_framework_mongoengine实现后端增删改查 ‍ 一、增删改查 1. 继承ModelViewSet实现增删改查 父urls.py path("api/testapp/", include("apps.testapp.urls")), # 测试子urls.py # -*- coding: utf-8 -*- from django.urls import path from res…...

【精读文献】Scientific data|2017-2021年中国10米玉米农田变化制图

论文名称&#xff1a;Mapping annual 10-m maize cropland changes in China during 2017–2021 第一作者及通讯作者&#xff1a;Xingang Li, Ying Qu 第一作者单位及通讯作者单位&#xff1a;北京师范大学地理学部 文章发表期刊&#xff1a;《Scientific data》&#xff08…...

高光谱图像修复笔记

目录 RetinexFormer 也有MST-plus-plus代码&#xff0c;分辨率可以调 MST-plus-plus github地址&#xff1a; WACV2023 DSTrans RetinexFormer GitHub - caiyuanhao1998/Retinexformer: "Retinexformer: One-stage Retinex-based Transformer for Low-light Image E…...

GPS定位原理及应用分析

一&#xff0e;定位原理 1.卫星定位&#xff08;GPS&#xff0c;北斗导航&#xff09; ①&#xff0e;硬件构成&#xff08;24颗卫星&#xff0c;可构建一套导航系统&#xff09; 为何是24颗卫星&#xff1f; 可以做到全球覆盖&#xff0c;同一地点地球上空可观测到4颗卫星。 …...

Java面试篇9——并发编程

并发编程知识梳理 提示&#xff0c;此仅为面试&#xff0c;若想对线程有跟完整了解&#xff0c;请点击这里 提示&#xff1a;直接翻到最后面看面试真题&#xff0c;上面的为详解 面试考点 文档说明 在文档中对所有的面试题都进行了难易程度和出现频率的等级说明 星数越多代表…...

[RK3399 Linux] 使用busybox 1.36.1制作rootfs

一、 编译、安装、配置 busybox 1.1 下载源码 根文件系统是根据busybox来制作的。 下载地址:https://busybox.net/downloads/。 这里就以1.36.1版本为例进行编译安装介绍: 注意:编译linux内核与文件系统中的所有程序要使用相同的交叉编译器。 下载完成后解压: mkdir …...

JavaScript入门--循环

JavaScript入门--循环 一、for循环二、for in语句三、break语句四、continue语句五、while循环六、do-while语句一、for循环 先来看一个循环案例: for (i = 0; i < 5; i++) {...

【Delphi 爬虫库 1】GET和POST方法

文章目录 1.最简单的Get方法实现2.可自定义请求头、自定义Cookie的Get方法实现3.提取响应协议头4.Post方法实现单词翻译 爬虫的基本原理是根据需求获取信息并返回。就像当我们感到饥饿时&#xff0c;可以选择自己烹饪食物、外出就餐&#xff0c;或者订外卖一样。在编程中&#…...

[leetcode] 快乐数 E

:::details 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果这个过程 结果为 1…...

Lobe UI - 基于 AntDesign 开发的 AIGC Web 应用的开源 UI 组件库

今天推荐一个可以快速开发 ChatGPT UI 界面的组件库&#xff0c;质量很高&#xff0c;拿来就能用。 Lobe UI 是由 lobehub 团队开发的一套 web UI 组件库&#xff0c;和我之前推荐的很多通用型的 UI 组件库不同&#xff0c;Lobe UI 是专门为目前火热的 AIGC 应用开发而打造&am…...

Java常用类 -- Random类

该类实例用于生成伪随机数的流 伪随机数&#xff1a;通过算法算出来的数&#xff0c;是假的随机数 &#xff08;一&#xff09;具体使用 public static void main(String[] args) { ​Random r new Random(); ​System.out.println("随机出int类型的数据" r.nextIn…...

Docker安装Kong网关

文章目录 一、kong是什么?二、搭建步骤1.搭建PostgreSQL2.搭建Kong网关2.1、制作镜像2.2、数据库初始化2.3、启动Kong网关一、kong是什么? Github地址:https://github.com/Kong/kong Kong是一个可扩展、开源的云原生API网关,可以在分布式环境中管理、监控和安全地发布API…...

spispispi

SPI C.. & C.. logic是SPI的控制逻辑&#xff0c;芯片内部进行地址锁存、数据读写等操作&#xff0c;都是由控制逻辑自动完成。控制逻辑的左边是SPI的通信引脚&#xff0c;这些引脚和主控芯片相连&#xff0c;主控芯片通过SPI协议&#xff0c;把指令和数据发送给控制逻辑&a…...

MySQL——创建和插入

一、插入数据 INSERT 使用建议; 在任何情况下建议列出列名&#xff0c;在 VALUES 中插入值时&#xff0c;注意值和列的意义对应关系 values 指定的值顺序非常重要&#xff0c;决定了值是否被保存到正确的列中 在指定了列名的情况下&#xff0c;你可以仅对需要插入的列给到…...

【BUG】element-ui表格中使用video标签,数据翻页,video中的视频仍然显示第一页的视频,没有重新加载

BUG描述 遇到一个问题&#xff0c;使用element-ui构建的管理端后台&#xff0c;表格里面每一行都有一个video标签&#xff0c;里面有视频&#xff0c;当我翻页了以后&#xff0c;视频不会重新加载&#xff0c;仍然显示的是第一页的视频&#xff0c;代码如下&#xff1a; <e…...

【JavaSE】你真的了解内部类吗?

前言 本篇会详细讲解内部类的四种形式&#xff0c;让你掌握内部类~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 内部类介绍 实例内部类 定义 调用 静态内部类 定义 调用 匿名内部类 定义和调用1 调用方法2 …...

Vue3(二):报错调试,vue3响应式原理、computed和watch,ref,props,接口

一、准备工作调试 跟着张天禹老师看前几集的时候可能会遇到如下问题&#xff1a; 1.下载插件&#xff1a;Vue Language Features (Volar)或者直接下载vue-offical 2.npm run serve时运行时出现错误&#xff1a;Error: vitejs/plugin-vue requires vue (&#xff1e;3.2.13) …...

前端console用法分享

console对于前端人员来讲肯定都不陌生&#xff0c;相信大部分开发者都会使用console来进行调试&#xff0c;但它能做的绝不仅限于调试。 最常见的控制台方法 作为开发者&#xff0c;最常用的 console 方法如下&#xff1a; 控制台打印结果&#xff1a; 今天我分享的是一些 co…...

Matlab|电价型负荷需求响应(考虑电价变化)

程序复现来源于《计及需求响应消纳风电的电-热综合能源系统经济调度 》第四章内容。 一、原理 需求响应的基本原理是需求侧根据电力市场价格和电网要求改变其负荷需求以 获取一定的利益回报。其中 PDR 可通过直观的电价变化信号引导用户调节用电方式&#xff0c; 从而达到优…...

PySide QWebChannel实现Python与JS双向通信的前后端分离桌面应用

文章目录 一、前言二、实现方法1.前端部分2.后端部分3.依赖文件三、运行结果一、前言 以往开发桌面应用通常都是页面接口一起写,这样开发周期比较长,且页面样式不灵活,如果能把页面交给前端写的话,就可前后端并行开发桌面应用了,并且css语言灵活好用样式丰富。下面介绍一…...

清明三天,用Python赚了4万?

每年4月&#xff0c;是Python圈子里接私活的旺季&#xff0c;特别是在节假日这种数据暴增的时间段&#xff0c;爬虫采集、逆向破解类的私活订单会集中爆发&#xff0c;量大价高。几乎所有的圈内人都在趁着旺季接私活。 正好&#xff0c;我昨天就做了一单爬虫逆向私活&#xff…...

【C/C++笔试练习】read函数、虚拟存储、用户态、线程特点、缺页处理、调度算法、进程优先级、锁的使用、创建进程、不用加减乘除做加法、三角形

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;read函数&#xff08;2&#xff09;虚拟存储&#xff08;3&#xff09;用户态&#xff08;4&#xff09;线程特点&#xff08;5&#xff09;缺页处理&#xff08;6&#xff09;调度算法&#xff08;7&#xff09;进程优先…...

设计模式(021)行为型之访问者模式

访问者模式是一种行为型设计模式&#xff0c;它可以在不修改现有代码结构的情况下&#xff0c;为复杂的对象结构添加新的操作。该模式将数据结构和数据操作进行分离&#xff0c;使得数据结构可以独立于操作进行变化&#xff0c;同时也可以在不改变操作的前提下增加新的操作。 在…...

Linux中磁盘的分区,格式化,挂载和文件系统的修复

一.分区工具 1.分区工具介绍 fdisk 2t及以下分区 推荐 (分完区不保存不生效&#xff0c;有反悔的可能) gdisk 全支持 推荐 parted 全支持 不推荐 ( 即时生效&#xff0c;分完立即生效) 2.fdisk 分区,查看磁盘 格式:fdisk -l [磁盘设备] fdisk -l 查看…...

Android retrofit

目录 一.简介 二.基本使用 三.注解 四.转换器 五.适配器 六.文件上传与下载 一.简介 A type-safe HTTP client for Android and Java。封装了OkHttp&#xff0c;也是由Square公司贡献的一个处理网络请求的开源项目。 square/retrofit: A type-safe HTTP client for Andr…...

【C++风云录】五款 C++ 库的探索与应用:物联网、嵌入式与数据处理

提升你的C技能&#xff1a;五个关键库的使用与指南 前言 在今天的数字化世界里&#xff0c;C 作为一种强大且快速的编程语言&#xff0c;在各类复杂系统和应用的开发中扮演着重要角色。然而&#xff0c;单凭语言本身的能力&#xff0c;我们往往无法实现所有的功能需求&#x…...

Qt_30道常见面试题及答案

1. 简述 Qt 是什么&#xff1f; 答&#xff1a;Qt 是一个跨平台的应用程序开发框架&#xff0c;它提供了一系列的工具和库&#xff0c;用于开发图形用户界面&#xff08;GUI&#xff09;应用程序。 2. Qt 有哪些主要模块&#xff1f; 答&#xff1a;Qt 的主要模块包括 Qt Co…...

【vue】v-model 双向数据绑定

:value&#xff1a;单向数据绑定v-model&#xff1a;双向数据绑定 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…...

C#创建磁性窗体的方法:创建特殊窗体

目录 一、磁性窗体 二、磁性窗体的实现方法 (1)无标题窗体的移动 (2)Left属性 (3)Top属性 二、设计一个磁性窗体的实例 &#xff08;1&#xff09;资源管理器Resources.Designer.cs设计 &#xff08;2&#xff09;公共类Frm_Play.cs &#xff08;3&#xff09;主窗体 …...

扬中网站优化公司/百度大全下载

来源&#xff1a; http://www.jobcoding.com/array/one-sorted-array/rotate_array/ 1. 问题描述 已知有序数组a[N]&#xff0c; 从中间某个位置k&#xff08;k未知&#xff0c;k-1表示整个数组有序&#xff09;分开&#xff0c;然后将前后两部分互换&#xff0c; 得到新的数组…...

贵州企业官网建设/优化公司怎么优化网站的

对于那些不能在Win 7上正常运行的程序&#xff0c;我的选择就是把这些程序装在XP Mode中。现在&#xff0c;XP Mode可以不需要硬件虚拟化 (例如 Intel的VT)的支持了。 from Windows 7 XP Mode FAQ XP Mode可以单独下载...

天津建设工程信息网网/湖南企业竞价优化服务

代  码 #输出元组变量 tuple (1,2,3,4,5,a,b,c) print(tuple) 运行结果 (1, 2, 3, 4, 5, a, b, c) 代  码 #输出字典变量 dict {a:1, b:2,c:3} print(dict) 运行结果 {a: 1, b: 2, c: 3...

如何做有后台的网站/广州seo关键字推广

https://blog.csdn.net/zhongzunfa/article/details/80344585...

哪个视频网站做直播销售/泉州关键词排名工具

2019独角兽企业重金招聘Python工程师标准>>> 访问权限修饰符&#xff1a;说明类中成员的访问权限 类中的成员按访问权限分为public&#xff08;公有&#xff09;、protected&#xff08;保护&#xff09;、priavte&#xff08;私有&#xff09;。 公有成员可以被该类…...

做网站有前途吗/东莞百度推广排名

MySQL 是一种高效快速的中小型数据库系统&#xff0c;这套系统的读写速度&#xff0c;尤其是读速度可以媲美和超过很多昂贵的商业数据库系统&#xff0c;同时其功能也完全可以满足一般网络应用软件的需要&#xff0c;适合为论坛等软件构建的数据库支撑环境。Discuz! 的 MySQL 版…...