实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
上一节课,我们做了一些理论知识的铺垫性讲解,讲到了两种开发模式,基于贫血模型的传统开发模式,以及基于充血模型的DDD开发模式。今天,我们正式进入实战环节,看如何分别用这两种开发模式,设计实现一个钱包系统。
话不多说,让我们正式开始今天的学习吧!
钱包业务背景介绍
很多具有支付、购买功能的应用(比如淘宝、滴滴出行等)都支持钱包的功能。应用为每个用户开设一个系统内的虚拟钱包账户,支持用户充值、提现、支付、冻结、透支、转赠、查询账户余额、查询交易流水等操作。下图是一张典型的钱包功能界面,你可以直观地感受一下。
一般来讲,每个虚拟钱包账户都会对应用户的一个真实的支付账户,有可能是银行卡账户,也有可能是三方支付账户(比如支付宝、微信钱包)。为了方便后续的讲解,我们限定钱包暂时只支持充值、提现、支付、查询余额、查询交易流水这五个核心的功能,其他比如冻结、透支、转赠等不常用的功能,我们暂不考虑。为了让你理解这五个核心功能是如何工作的,接下来,我们来一块儿看下它们的业务实现流程。
1.充值
用户通过三方支付渠道,把自己银行卡账户内的钱,充值到虚拟钱包账号中。这整个过程,我们可以分解为三个主要的操作流程:第一个操作是从用户的银行卡账户转账到应用的公共银行卡账户;第二个操作是将用户的充值金额加到虚拟钱包余额上;第三个操作是记录刚刚这笔交易流水。
2支付
用户用钱包内的余额,支付购买应用内的商品。实际上,支付的过程就是一个转账的过程,从用户的虚拟钱包账户划钱到商家的虚拟钱包账户上。除此之外,我们也需要记录这笔支付的交易流水信息。
3.提现
除了充值、支付之外,用户还可以将虚拟钱包中的余额,提现到自己的银行卡中。这个过程实际上就是扣减用户虚拟钱包中的余额,并且触发真正的银行转账操作,从应用的公共银行账户转钱到用户的银行账户。同样,我们也需要记录这笔提现的交易流水信息。
4.查询余额
查询余额功能比较简单,我们看一下虚拟钱包中的余额数字即可。
5.查询交易流水
查询交易流水也比较简单。我们只支持三种类型的交易流水:充值、支付、提现。在用户充值、支付、提现的时候,我们会记录相应的交易信息。在需要查询的时候,我们只需要将之前记录的交易流水,按照时间、类型等条件过滤之后,显示出来即可
钱包系统的设计思路
根据刚刚讲的业务实现流程和数据流转图,我们可以把整个钱包系统的业务划分为两部分,其中一部分单纯跟应用内的虚拟钱包账户打交道、另一部分单纯跟银行账户打交道。我们基于这样一个业务划分,给系统解耦,将整个钱包系统拆分为两个子系统:虚拟钱包系统和三方支付系统。
为了能在有限的篇幅内,将今天的内容讲透彻,我们接来下只聚焦于虚拟钱包系统的设计与实现。对干三方查付系统以是警个钱包系统的设计与实现,我们不做讲解。你可以自己思考下。
现在我们来看下,如果要支持钱包的这五个核心功能,虚拟钱包系统需要对应实现哪些操作。我画了一张图,列出了这五个功能都舍对应虚拟钱包的哪些操作。注意,交易流水的记录和查询,我暂时在图中打了个问号,那是因为这块比较特锋,我们待会再讲
从图中我们可以看出,虚拟钱包系统要支持的操作非常简单,就是余额的加加减减。其中,充值 提现 查询余额三个功能,只涉及一个账户余额的加减操作,而支付功能涉及两个账户的余额加减操作:一个账户减余额,另一个账户加余额。
现在,我们再来看一下图中问号的那部分,也就是交易流水该如何记录和查询?我们先来看一下,交易流水都需要包含哪些信息。我觉得下面这几个信息是必须包含的。
从图中我们可以发现,交易流水的数据格式包含两个钱包账号,一个是入账钱包账号,一个是出账钱包账号。为什么要有两个账号信息呢?这主要是为了兼容支付这种涉及两个账户的交易类型。不过,对于充值、提现这两种交易类型来说,我们只需要记录一个钱包账户信息就够了。
整个虚拟钱包的设计思路到此讲完了。接下来,我们来看一下,如何分别用基于贫血模型的传统开发模式和基于充血模型的DDD开发模式,来实现这样一个虚拟钱包系统?
基于贫血模型的传统开发模式
实际上,如果你有一定Web项目的开发经验,并且听明白了我刚刚讲的设计思路,那对你来说,利用基于贫血模型的传统开发模式来实现这样一个系统,应该是一件挺简单的事情。不过,为了对比两种开发模式,我还是带你一块儿来实现一-遍。
这是一个典型的Web后端项目的三层结构。其中,Controller和VO负责暴露接口,其体的代码实现如下所示。注意, Controller中,接口实现比较简单,主要就是调用Service的方法,所以,我省略了具体的代码实现。
public class VirtualWalletController (){
//通过构造函数或者10c框架注入
private VirtualWalletService virtualWalletService;
public BigDecimal getBalance(Long walletId){} //查询余额
public void debit(Long walletId, BigDecimal amount){}//出账
public void credit(Long walletId, BigDecimal amount) //入账
public void transfer(Long fromWalletid, Long towalletid, BigDecimal amount){} //转
//省略查询transaction的接口
Service和BO负责核心业务逻辑,Repository和Entity负责数据存取。Repository这一层的代码实现比较简单,不是我们消繁的重点,所以我也省略掉了。Service层的代码如下所示。注意,这里我省略了一些不重要的校验代码,比如,对amount是否小于0,钱包是否存在约校验等等。
public class VirtualwalletBo (//省略getter/settet/constructor方法
private Long id;
private Long createTime;
private BigDecimal balance;
public Enum TransactionType{
DEBIT,
CREDIT,
TRANSFER;
}
public class VirtualWalletService (//通过构造函数或者IOC框架注入private VirtualWalletRepository walletRepo;private VirtualWalletTransactionRepository transactionRepo;public VirtualWalletBo getVirtualwallet(Long walletId) {VirtualwalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWalletBo walletBo = convert(walletEntity);return walletBo;}public BigDecimal getBalance(Long walletid){return walletRepo.getBalance(walletid);}@Transactionalpublic void debit(Long walletid, BigDecimal amount)VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletld);BigDecimal balance = walletEntity.getBalance();if (balance.compareTo(amount) < 0){throw new NosufficientBalanceException(..);}VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount (amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, balance.subtract(amount));}@Transactionalpublic void credit(Long walletId, BigDecimal amount){VirtualwalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setcreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromwalletId(walletId);transactionRepo.saveTransaction(transactionEntity);VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);BigDecimal balance = walletEntity,getBalance();walletRepo.updateBalance(walletId, balance.add(amount));}@Transactionalpublic void transfer(Long fromwalletId, Long towalletId, BigDecimal amount) {VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity(): transactionEntity.setAmount (amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.TRANSFER);transactionEntity,setFromWalletId(fromwalletId);transactionEntity.setTowalletId(towalletId);transactionRepo.saveTransaction(transactionEntity);debit(fromWalletId, amount);credit(towalletId, amount);}
基于充血模型的DDD开发模式
刚刚讲了如何利用基于贫血模型的传统开发模式来实现虚拟钱包系统,现在,我们再来看一下,如何利用基于充血模型的开发模式来实现这个系统?
在上一节课中,我们讲到,基于充血模型的DDD开发模式,跟基于贫血模型的传统开发模式的主要区别就在Service层,Controller展和Repository层的代码基本上相同。所以,我们重点看一下,Service层按照基于充血模型的DDD开发模式该如何来实现。
在这种开发模式下,我们把虚拟钱包VirtuaWallet类设计成一个充血的Domain领域模型,并且将原来在Senvice类中的部分业务逻辑移动到VirtualWallet类中,让Service类的实现依赖VirtualWallet类。具体的代码实现如下所示:
public class Virtualwallet {//Domain领城模型/充血模型private Long id;private Long createTime =System.currentTimeMillis();;、private Bigbecimal balance = Bigbecimal.ZERO;public virtualWallet(Long preAllocatedid) {this.id = preAllocatedId;}public Bigbecimal balance(){return this.balance;}public void debit(Bigbecimal amount){if(this.balance.compareTo(amount) <0)throw new InsufficientBalanceException()}this.balance this.balance.subtract(amount);}public void credit(BigDecimal amount){if (amount.compareTo(BigDecimal.ZERO) <0){throw new InvalidAmountException(...);}this.balance = this.balance.add(amount);}public class virtualWalletServicef{//通过构造函数或者1OC框架注入private virtualWalletRepository walletRepo;private virtualwalletTransactionRepository transactionRepo;public Virtualwallet getvirtualWallet(Long walletid){VirtualwalletEntity walletEntity = walletRepo.getwalletEntity(walletld);Virtualwallet wallet = convert(walletEntity);return wallet;}public BigDecimal getBalance(Long walletra){return walletRepo.getBalance(walletId);}@Transactionalpublic void debit(Long walletid, BigDecinal amount){VirtualwalletEntity walletEntity = walletRepo.getwalletEntity(walletId);Virtualwallet wallet = convert(walletEntity);wallet.debit(amount);VirtualWalletTransactionEntity transactionEntity = new VirtualwalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTine(Systen.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromwalletId(walietId):transactionRepo.saveTransaction(transactionEntity)walletRepo.updateBalance(walletId, wallet.balance());}@Transactionalpublic void credit(Long walletId, Bigpectmal amount){VirtualWalletEntity walletEntity walletRepo.getWalletEntity(walletid);VirtualWallet wallet convert(walletEntity);wallet.credit (amount);VirtualWalletTransactionEntity transactionEntity new VirtualWalletTransactionEntity();transactionEntity.setAmount (amount);transactionEntity.setCreateTime(System.qurrentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletid, wallet.balance());}@Transactionalpublic void transfer(Long fromwalletId, Long towalletId, BigDecimal amount){// 跟基于贫血模型的传统开发模式的代码一样...}
}
看了上面的代码,你可能会说,领域模型VirtualWallet类很单薄,包含的业务逻辑很简单。相对于原来的贫血模型的设计思路,这种充血模型的设计思路,貌似并没有太大优势。你说得没错!这也是大部分业务系统都使用基于贫血模型开发的原因。不过,如果虚拟钱包系统需要支持更复杂的业务逻辑,那充血模型的优势就显现出来了。比如,我们要支持透支一定额度和冻结部分余额的功能。这个时候,我们重新来看一下VirtualWallet类的实现代码。
public class Virtualwallet{private Long id;private Long createTime = System.currentTimeMillis();private BigDecimal balance = BigDecimal.ZERO;private boolean isAllowedOverdraft = true;private BigDecimal overdraftAmount = BígDecimal.ZERO;private BigDecimal frozenAmount = BigDecimal.ZERO;public VirtualWallet(Long preAllocatedId) {this.id = preAllocatedId;public void freeze(BigDecimal amount) { ... }public void unfreeze(BigDecimal amount) { ...}public void increaseoverdraftAmount(BigDecimal amount) { ...}public void decreaseOverdraftAmount(BigDecimal amount) { ...}public void closeOverdraft() { ... }publíc void openoverdraft() { ...}public BigDecimal balance() {return this.balance;}public BigDecimal getAvaliableBalance() {BigDecimal totalAvaliableBalance = this.balance.subtract(this.frozenAmount);if (isAllowedoverdraft) {totalAvaliableBalance += this.overdraftAmount;}return totalAvaliableBalance;}public void debit(BigDecimal amount) {BigDecimal totalAvaliableBalance = getAvaliableBalance();if (totoalAvaliableBalance.compareTo(amount) < 0) {throw new InsufficientBalanceException("");}this.balance = this.balance:subtract(amount.)}}public void credit(BigDecimal amount){if (amount.compareTo(BigDecimal.ZERO) <0) {throw new InvalidAmountException(...);}this.balance = this.balance.add(amount);}
领域模型VirtualWallet类添加了简单的冻结和透支逻辑之后,功能看起来就丰富了很多,代码也没那么单薄了。如果功能继续演进,我们可以增加更加细化的冻结策略、透支策略、支持钱包账号(Virtualwailet id字段)自动生成的逻辑(不是通过构造函数经外部传入ID,而是通过分布式ID生成算法来自动生成ID)等等。VirtualWallet类的业务逻辑会变得越来越复杂,也就很值得设计成充血模型了。
辩证思考与灵活应用
对于虚拟钱包系统的设计与两种开发模式的代码实现,我想你应该有个比较清晰的了解了。不过,我觉得还有两个问题值得讨论一下。
第一个要讨论的问题是:在基于充血模型的DDD开发模式中,将业务逻辑移动到Domain中, Service类变得很薄,但在我们的代码设计与实现中,并没有完全将Service类去掉,这是为什么?或者说, Service类在这种情况下担当的职责是什么?哪些功能逻辑会放到Service类中?
区别于Domain的职责,Service类主要有下面这样几个职责。
1.Service类负责与Repository交流。在我的设计与代码实现中, VirtualWalletService类负责与Repository层打交道,调用Respository类的方法,获取数据库中的数据,转化成领域模型VirtualWallet,然后由领域模型VirtualWallet来完成业务逻辑,最后调用Repository类的方法,将数据存回数据库。
这里我再稍微解释一下,之所以让VirtualWalletService类与Repository打交道,而不是让领域模型VirtualWallet与Repository打交道,那是因为我们想保持领域模型的独立性,不与任何其他层的代码(Repository层的代码)或开发框架(比如Spring, MyBatis)耦合在一起.将流程性的代码逻辑(比如从DB中取数据、映射数据)与領域模型的业务逻辑解耦,让领域模型更加可复用。
2.Service类负责跨领域模型的业务聚合功能。VirtuniwalletService类中的transfer)转账函数会涉及两个钱包的操作,因此这部分业务逻辑无法放到VirtualWallet类中,所以,我们暂且把转账业务放到VirtualWalletService类中了。当然,虽然功能演进,使得转账业务变得复杂起来之后,我们也可以将转账业务抽取出来,设计成一个独立的领域模型。
3.Service类负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消息、记录日表、调用其他系统的RPC接口等都可以放到Service类中。
第二个要讨论问题是:在基于充血模型的DDD开发模式中,尽管Service层被改造成了充血模型,但是Controller层和Repository层还是贫血模型,是否有必要也进行充血領域建模呢?
答案是没有必要,Controller层主要负责接口的暴露, Repository层主要负责与数据库打交道,这两层包含的业务逻辑并不多,前面我们也提到了,如果业务逻辑比较简单,就没必要做充血建模,即便设计成充血模型,类也非常单薄、看起来也很奇怪。
尽管这样的设计是一种面向过程的编程风格,但我们只要控制好面向过程编程风格的副作用,照样可以开发出优秀的软件。那这里的副作用怎么控制呢?
就拿Repository的Entity来说,即便它被设计成贫血模型,违反面向对象编程的封装特性,有被任意代码修改数据的风险,但Entity的生命周期是有限的。一般来讲,我们把它传递到Service层之后,就会转化成BO或者Domain来继续后面的业务逻辑。Entity的生命周期到此就结束了,所以也并不会被到处任意修改。
我们再来说说Controller层的vo,实际上VO是一种DTO (Data Transfer Object,数据传输对象)。它主要是作为接口的数据传编承载体,将数据发送给其他系统。从功能上来讲,它理应不包含业务逻辑、只包含数据。所以、我们将它设计成贫血模型也是比较合理的
重点回顾
今天的内容到此就讲完了。我们一块来总结回顾一下,你应该重点掌握的知识点。
基于充血模型的DDD开发模式跟基于贫血模型的传统开发模式相比,主要区别在Service层。在基于充血模型的开发模式下,我们将部分原来在Service类中的业务逻辑移动到了一个充血的Domain领域模型中,让Service类的实现依赖这个Domain类。
在基于充血模型的DDD开发模式下,Service类并不会完全移除,而是负责一些不适合放在Domain类中的功能。比如,负责与Repository打交到、跨领域模型的业务聚合功能、需等事务等非功能性的工作。
基于充血模型的DDD开发模式跟基于贫血模型的传统开发模式相比, Controller层和Repository层的代码基本上相同,这是因为
Repository层的Entity生命周期有限,Controller层的VO只是单纯作为一种DTO,两部分的业务逻辑都不会太复杂。业务逻辑主要集中在Service层。所以, Repository层和Controller层继续沿用贫血模型的设计思路是没有问趣的。
相关文章:
实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
上一节课,我们做了一些理论知识的铺垫性讲解,讲到了两种开发模式,基于贫血模型的传统开发模式,以及基于充血模型的DDD开发模式。今天,我们正式进入实战环节,看如何分别用这两种开发模式,设计实现一个钱包系统。话不多说,让我们正式…...
webpack当中的代码分割详解
A.代码分割方法一:将原来的单入口文件改为多入口文件 将不同的文件例如js代码文件分为入口文件和测试文件,这个时候打包出来的代码就会根据不同的文件单独打包成属于他们自己的文件 例如以下为单入口文件: entry: ./src/js/index.js 多入口文件:(在输出…...
【SSM】Spring对IoC的实现方式DI详讲
控制反转的一种实现方式——依赖注入一、IoC 控制反转(Overview)依赖注入(DI)- Overview利用 IoC(控制反转)这种思想有什么好处呢?二、依赖注入的方式setter 方式(xml配置中的proper…...
【QT 5 相关实验-示波器-学习笔记-示波器组件练习与使用总结】
【QT 5 相关实验-示波器-学习笔记-示波器组件练习与使用总结】1、概述2、实验环境3、参考资料-致谢4、自我提升实验效果视频演示5、代码练习-学习后拆解-实验步骤(1)头文件部分-"mwaveview.h"(2)cpp文件部分-"mwav…...
二维数组中的查找(两种解法,各有千秋)
凡事都有可能,永远别说永远。——《放牛班的春天》今天一题为再一个行列都有序的二维数组中寻找一个目标值,我们第一时间想到的可能是很暴力的解法,例如从头到尾进行遍历,这样能做出来,但是借用武忠祥老师的一句话&…...
quartz使用及原理解析
quartz简介 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能: 持久性作业 - 就是保持调度…...
Datawhale组队学习:大数据 D2——分布式文件系统(HDFS)
妙趣横生大数据 Day2三、Hadoop 分布式文件系统(HDFS)1. 分布式文件系统2. HDFS 简介3. HDFS 体系结构4. HDFS存储原理数据冗余存储数据存储策略数据错误与恢复5. HDFS数据读写过程读写过程HDFS故障类型和其检测方法HDFS编程实验1. 本地和集群文件间操作2. 基本文件操作3. Hado…...
CCIE重认证-300-401-拖图题全
拖图 拖图题 编程 snippet;192.168.5.0,mask 255.255.255.0;number是192.168.5.0;mask是255.255.255.0 snippets;edit-config对config,loopback对name 100,address对primary,mask…...
如何动态的创建类?type的其他用法?什么是元类,如何自定义元类?
1、python中一切都是对象,类也不例外,type是object的子类,是创建类的类。 如何动态的创建一个类? 用脚丫子创建 用脑子创建 不会 不知道什么事动态类 大家可能会有一堆的疑惑,是的我也是有很多疑惑那让我们一起来探个…...
XCP实战系列介绍15-XCP故障排查指导
本文框架 1.概述2. 通过调试器排查2.1 打开Det功能2.2 如何确定Det ErrorCode3. 通过XCP应答报文排查3.1 FE报文组成及故障码对应关系3.2 举个例子1.概述 前面几篇文章我们介绍了基于Davinci开发工具的XCP配置指导,配好了,代码也生成了,但是程序一定能正常跑起来吗?就算软…...
吉林大学软件需求分析与规范(Software Requirements Analysis Specification)
chapter0课程简介:◼ 软件工程专业核心课程之一◼ 软件工程课程体系最前端课程◼ 主要内容:需求的基本概念,需求的分类,需求工程的基本过程,需求获取的方法、步骤、技巧,需求分析和建模技术,需求…...
PyTorch - Conv2d 和 MaxPool2d
文章目录Conv2d计算Conv2d 函数解析代码示例MaxPool2d计算函数说明卷积过程动画Transposed convolution animationsTransposed convolution animations参考视频:土堆说 卷积计算 https://www.bilibili.com/video/BV1hE411t7RN 关于 torch.nn 和 torch.nn.function t…...
leetcode Day2(昨天实习有点bug,心态要崩了)
int carry 0;for(int i a.size() - 1, j b.size() - 1; i > 0 || j > 0 || carry; --i, --j) {int x i < 0 ? 0 : a[i] - 0;int y j < 0 ? 0 : b[j] - 0;int sum (x y carry) % 2;carry (x y carry) / 2;str.insert(0, 1, sum 0);}return str;加一&a…...
另一种思考:为什么不选JPA、MyBatis,而选择JDBCTemplate
以下内容转载自:https://segmentfault.com/a/1190000018472572 作者:scherman 因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框架,对于复杂业务系统,最终的结论是,JOOQ是总体上最好的…...
LeetCode 338. 比特位计数
给你一个整数 n ,对于 0 < i < n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n 1 的数组 ans 作为答案。 示例 1: 输入:n 2 输出:[0,1,1] 解释: 0 --> 0 1 --> …...
排序评估指标——NDCG和MAP
在搜索和推荐任务中,系统常返回一个item列表。如何衡量这个返回的列表是否优秀呢? 例如,当我们检索【推荐排序】,网页返回了与推荐排序相关的链接列表。列表可能会是[A,B,C,G,D,E,F],也可能是[C,F,A,E,D],现在问题来了…...
[Android Studio] Android Studio Virtual Device(AVD)虚拟机的功能试用
🟧🟨🟩🟦🟪 Android Debug🟧🟨🟩🟦🟪 Topic 发布安卓学习过程中遇到问题解决过程,希望我的解决方案可以对小伙伴们有帮助。 🚀write…...
kafka-3-kafka应用的核心要点和内外网访问
kafka实战教程(python操作kafka),kafka配置文件详解 Kafka内外网访问的设置 1 kafka简介 根据官网的介绍,ApacheKafka是一个分布式流媒体平台,它主要有3种功能: (1)发布和订阅消息流,这个功能类似于消息队列&#x…...
VS2017+OpenCV4.5.5 决策树-评估是否发放贷款
决策树是一种非参数的监督学习方法,主要用于分类和回归。 决策树结构 决策树在逻辑上以树的形式存在,包含根节点、内部结点和叶节点。 根节点:包含数据集中的所有数据的集合内部节点:每个内部节点为一个判断条件,并且…...
Prometheus 记录规则和警报规则
前提环境: Docker环境 涉及参考文档: Prometheus 录制规则Prometheus 警报规则 语法检查规则 promtool check rules /path/to/example.rules.yml一:录制规则语法 groups 语法: groups:[ - <rule_group> ]rule_group…...
(API)接口测试的关键技术
接口测试也就是API测试,从名字上可以知道是面向接口的测试活动。所以在讲API测试之前,我们应该说清楚接口是什么,那么接口就是有特定输入和特定输出的一套逻辑处理单元,而对于接口调用方来说,不用知道自身的内部实现逻…...
快速排序算法原理 Quicksort —— 图解(精讲) JAVA
快速排序是 Java 中 sort 函数主要的排序方法,所以今天要对快速排序法这种重要算法的详细原理进行分析。 思路:首先快速排序之所以高效一部分原因是利用了离散数学中的传递性。 例如 1 < 2 且 2 < 3 所以可以推出 1 < 3。在快速排序的过程中巧…...
linux环境搭建私有gitlab仓库
搭建之前,需要安装相应的依赖包,并且要启动sshd服务(1).安装policycoreutils-python openssh-server openssh-clients [rootVM-0-2-centos ~]# sudo yum install -y curl policycoreutils-python openssh-server openssh-clients [rootVM-0-2-centos ~]…...
SpringSecurity授权
文章目录工具类使用自定义失败处理代码配置跨域其他权限授权hasAnyAuthority自定义权限校验方法基于配置的权限控制工具类 import javax.servlet.http.HttpServletResponse; import java.io.IOException;public class WebUtils {/*** 将字符串渲染到客户端** param response 渲…...
学习 Python 之 Pygame 开发坦克大战(一)
学习 Python 之 Pygame 开发坦克大战(一)Pygame什么是Pygame?初识pygame1. 使用pygame创建窗口2. 设置窗口背景颜色3. 获取窗口中的事件4. 在窗口中展示图片(1). pygame中的直角坐标系(2). 展示图片(3). 给部分区域设置颜色5. 在窗口中显示文字6. 播放音…...
2.5|iot冯|方元-嵌入式linux系统开发入门|2.13+2.18
一、 Linux 指令操作题(共5题(共 20 分,每小题 4分)与系统工作、系统状态、工作目录、文件、目录、打包压缩与搜索等主题相关。1.文件1.1文件属性1.2文件类型属性字段的第1个字符表示文件类型,后9个字符中,…...
一起Talk Android吧(第四百九十六回:自定义View实例二:环形进度条)
文章目录 知识回顾实现思路实现方法示例代码各位看官们大家好,上一回中咱们说的例子是"如何使用Java版MQTT客户端",这一回中咱们说的例子是"自定义View实例二:环形进度条"。闲话休提,言归正转,让我们一起Talk Android吧! 知识回顾 看官们,我们又回…...
上传图片尺寸校验
使用方法 ● Image ● URL ● onload代码: async validImageSize(file, imgWidth, imgHeight) {const img new Image()img.src URL.createObjectURL(file)const { w, h } await new Promise((resolve, reject) > {img.onload () > {const { width: w, he…...
【Python】缺失值处理和拉格朗日插值法(含源代码实现)
目录:缺失值处理和拉格朗日插值法一、前言二、理论知识三、代码实现一、前言 对于含有缺失值的数据集,如果通过删除小部分记录达到既定的目标,那么删除含有缺失值的记录的方法是最有效的。然而,这种方法也有很多问题,…...
SpringCloudAlibaba-Sentinel
一、介绍官网:https://github.com/alibaba/Sentinel/下载jar包,启动,访问http://localhost:8080/创建module添加如下依赖<!--SpringCloud ailibaba sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring…...
做cpa用什么网站/网站百度权重
在使用Ueditor时,如要简化工具栏上的按钮,可以修改配置项的方法: 1. 方法一:修改 ueditor.config.js 里面的 toolbars 2. 方法二:实例化编辑器的时候传入 toolbars 参数 我一般用第二种方法, <script sr…...
为什么要建设学校网站/建材企业网站推广方案
模拟实现C智能指针shared_ptr和weak_ptr 仿写C的shared_ptr和weak_ptr 当强智能指针shared_ptr的引用计数为0时,析构资源 当弱智能指针weak_ptr的引用计数为0时,析构引用计数对象 #include<iostream> #include<new> #include<stdio.h&g…...
广州知名网站/5118素材网站
2019独角兽企业重金招聘Python工程师标准>>> 1、数组的概念 数组是相同类型的变量的有序集合 int a[5]; 数组包含5个int类型的数据 a代表数组第一个元素的起始地址,这20个字节的名字为a。a[0], a[1]等都是a中的元素,并非元素的名字࿰…...
wordpress head.php/dw网页设计模板网站
如果你把oracle11g装在笔记本上并让服务开机启动的话,会明显感受到笔记本比平时启动慢几十秒, 差点的甚至1-2分钟,但是不开机启动吧,每次到服务里打开,很麻烦... 用批处理文件打开和关闭不失为一个好办法。 -------…...
网站设计中 查询怎么做/手机优化器
1准备工作 , 加载apicloud 海康视频模块。 引入 SDK 重新生成项目测试 再config.xml写入appid 话不多说直接上代码 videoapi.require("haikangVideo");video.open({ip:ip,port:port,channel:1,user:name,pwd:pwd,fixedOn: api.frameName,fixed: true,rect : {x : …...
上海企业网站设计制作/销售网站排名
hash赋能前言一、缺失的第一个正数二、hash赋能1、hashSet2、原地数组hash总结参考文献前言 hash赋能可以为后面的工作大大减少搜索的时间,可用hash数组、hashSet、hashMap、原地数组hash。 一、缺失的第一个正数 二、hash赋能 1、hashSet //hash赋能//Time:O(n…...