“饿了吧“项目电子钱包实验报告
一、需求分析:增加电子钱包功能
1. 背景
饿了吧项目原先没有电子钱包功能,支付都是通过银行卡或第三方支付(如支付宝、微信支付)完成。这种方式的局限性在于系统无法实现充值返点、退款暂存、透支、收款等功能,因此增加一个电子钱包功能变得必要。大部分电商平台都有钱包功能用于管理平台内的资金,提高用户黏性,方便资金流动。此次新增电子钱包功能以提升系统的灵活性和功能的丰富度。
2. 功能需求
- 充值和提现功能:
- 用户可以向钱包充值和从钱包中提现。
- 为鼓励用户将资金存入平台,充值时应有一定奖励。例如,充值满1000元赠送100元。提现时,充值时所得的奖励部分需扣除,以防止利用奖励规则的“薅羊毛”行为。
- 奖励和手续费规则应支持动态设置,以应对不同的市场情况。
- 支付和转账功能:
- 用户可以使用钱包进行支付和向其他钱包转账。
- 支付过程应保证交易的原子性,确保支付成功后资金准确从付款方钱包转入收款方钱包,避免因网络或系统问题造成资金丢失或不一致。
- 余额查询和交易流水查询功能:
- 用户可以随时查询电子钱包的当前余额。
- 用户可以查看电子钱包的所有交易流水,了解资金的流动情况。
- 冻结和透支功能:
- 冻结功能:买家支付后,货款进入卖家账户但处于冻结状态,待买家确认收货后方可解冻,以保障买卖双方的权益。
- 透支功能:为VIP用户提供一定的透支额度。如果用户未在规定时间内归还透支款项,则需支付一定利息,这类似于小额贷款或支付宝花呗的功能。
3. 系统设计和划分
- 虚拟钱包系统:负责管理用户在平台内的虚拟账户。所有与虚拟资金相关的功能,如充值、提现、支付、转账、冻结、透支等,均由该系统处理。
- 三方支付系统:负责与用户的银行账户或第三方支付平台交互。当用户进行充值或提现操作时,三方支付系统与银行账户进行交互,将资金转入或转出平台内的虚拟钱包。
通过这种系统的划分,可以有效解耦虚拟钱包和外部资金的交互,减少系统的耦合度,提升可维护性和安全性。虚拟钱包系统聚焦于平台内资金的管理,而三方支付系统则专注于与外部支付渠道的对接。
二、方案设计
1、总体设计方案
在“饿了吧”项目中增加电子钱包功能,旨在实现平台内的资金管理,包括充值、提现、支付、查询余额、查询交易流水等核心功能。钱包功能被拆分为虚拟钱包系统和第三方支付系统两个子系统。虚拟钱包系统主要负责用户资金的存储和管理,而第三方支付系统负责与外部银行账户或支付渠道的对接。为了更好地实现面向对象的封装和业务逻辑集中管理,本次设计采用了基于充血模型的领域驱动设计(DDD)方法,特别在虚拟钱包模块中应用了充血模型。
2、充血模型的应用
在本次方案中,虚拟钱包模块采用充血模型来管理钱包的业务逻辑和数据状态。与传统贫血模型相比,充血模型将数据与相关业务逻辑封装到同一个类中。这种设计符合面向对象编程的封装特性,能够提高代码的聚合度和内聚性,从而更好地保证业务逻辑的一致性。
具体地,在“饿了吧”项目中,VirtualWallet
类被设计为充血模型:
VirtualWallet
类不仅保存用户钱包的基本信息(如余额、冻结金额、透支额度等),还直接包含了充值、提现、转账、冻结资金、解冻资金等业务方法。- 例如,在用户充值的场景中,
recharge
方法被封装在VirtualWallet
类中,业务逻辑包括增加余额、计算奖励金额并存入奖励余额字段,这些操作都集中在领域对象中完成,而不是分散到服务层中。
这种设计方式的优势在于:
- 高内聚性:数据和操作紧密结合,减少了业务逻辑分散于多个类的情况,使代码结构更加简洁。
- 清晰的职责划分:将钱包管理的核心逻辑放在领域对象中,服务层的职责被简化为调用领域对象的方法,起到协调控制的作用,而非直接处理业务逻辑。
- 增强可维护性:充血模型使得业务逻辑的更新集中在一个地方,减少了代码维护的难度和可能出现的错误。
3、模块设计与接口方案
- VirtualWallet领域模型类
- 封装钱包的核心数据和操作,包括余额、冻结金额、透支额度、奖励余额等字段,以及
recharge
、withdraw
、transferTo
、freeze
、unfreeze
等方法。 - 通过充血模型,将余额管理、奖励计算、资金冻结等逻辑放入领域对象中,减少了代码分散的情况。
- 封装钱包的核心数据和操作,包括余额、冻结金额、透支额度、奖励余额等字段,以及
- 数据访问层(Mapper层)
- 使用
MyBatis
作为ORM工具,通过注解的方式实现对数据库的访问。 - 例如,
VirtualWalletMapper
用于从数据库中获取钱包信息、保存或更新钱包状态,TransactionLogMapper
用于保存和查询交易日志。 - 每个方法与数据库中的操作保持一致,以确保数据的完整性与一致性。
- 使用
- 服务层(Service层)
VirtualWalletService
接口定义了钱包的主要操作,如充值、提现、转账、冻结和解冻资金等,VirtualWalletServiceImpl
实现了具体的逻辑。- 服务层的主要职责是协调领域模型对象,调用
VirtualWallet
的业务方法,处理操作的事务管理以及与数据访问层的交互。
- 控制器层(Controller层)
VirtualWalletController
用于暴露API接口,供前端或其他服务调用。- 控制器层接收用户的请求,解析参数并调用服务层方法。控制器仅负责用户请求的处理和响应的生成,具体的业务逻辑则委托给服务层和领域模型对象处理。
- 例如,充值接口通过调用
walletService.recharge()
方法完成,服务层再进一步调用VirtualWallet
的充血模型方法实现核心业务逻辑。
4、接口实现与流程
本次钱包功能中实现的主要接口包括:
- 充值接口:
- 路径:
POST /wallet/recharge
- 参数:
walletId
、amount
、rewardRate
- 流程:控制器层调用服务层的
recharge
方法,服务层获取钱包对象并调用VirtualWallet
的recharge
方法更新余额和奖励。
- 路径:
- 提现接口:
- 路径:
POST /wallet/withdraw
- 参数:
walletId
、amount
、feeRate
- 流程:调用服务层的
withdraw
方法,服务层从数据库获取钱包对象并调用withdraw
方法处理扣款逻辑,并在完成后更新数据库。
- 路径:
- 转账接口:
- 路径:
POST /wallet/transfer
- 参数:
fromWalletId
、toWalletId
、amount
- 流程:控制器接收参数,服务层获取两个钱包对象,通过调用
VirtualWallet
的transferTo
方法完成资金的转移。
- 路径:
- 交易日志查询接口:
- 路径:
GET /wallet/{walletId}/transactions
- 参数:
walletId
- 流程:服务层调用
TransactionLogMapper
查询指定钱包的所有交易日志,返回给调用方。
- 路径:
5、数据库的设计
根据需求我们可以设计如下数据库schemas


三、代码实现
先根据数据库的设计编写出相应的sql语句,并插入几条测试数据
1 | -- 钱包表 (wallet) |
然后,根据需求,我们需要新增VirtualWallet类和TransactionLog类,并且在这两个类中实现各自的方法
VirtualWallet
VirtualWallet类的实体属性
1 | private String walletId; |
然后写出相应的构造函数和getter和setter方法
实现充值、提现、支付和接收金额的基本操作 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 充值操作
public void recharge(BigDecimal amount, BigDecimal rewardRate) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("充值金额必须大于零");
}
BigDecimal reward = amount.multiply(rewardRate);
this.balance = this.balance.add(amount);
this.rewardBalance = reward;
}
// 提现操作
public void withdraw(BigDecimal amount, BigDecimal feeRate) throws Exception {
BigDecimal fee = amount.multiply(feeRate);
BigDecimal totalAmount = amount.add(fee);
if (totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("提现金额必须大于零");
}
if (this.balance.compareTo(totalAmount) < 0) {
throw new Exception("余额不足");
}
this.balance = this.balance.subtract(totalAmount);
}
// 支付操作
public void transferTo(VirtualWallet targetWallet, BigDecimal amount) throws Exception {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("转账金额必须大于零");
}
if (this.getAvailableBalance().compareTo(amount) < 0) {
throw new Exception("可用余额不足");
}
this.balance = this.balance.subtract(amount);
targetWallet.receive(amount);
}
实现冻结资金、解冻资金等高级操作
1 | // 冻结资金 |
然后写出相应的Controller层、Service层和ServiceImpl层,由于篇幅原因只给出一部分
VirtualWalletController
1 |
|
Service层
1 | public interface VirtualWalletService { |
ServiceImpl层
1 |
|
最后完成VirtualWalletMapper层的撰写,注意,这里的实体类和数据库中的字段名不一致,应当用@Result来进行一一对应
1 |
|
TransactionLog
TransactionLog实体类
1 | private int transactionId; // 交易唯一标识 |
然后给出相应的构造函数、getter和setter方法,注意这里需要新增一个无参构造的方法,具体原因见
然后要更新交易状态并获取交易详细信息的简化展示
1 | // 更新交易状态 |
TransactionLogMapper
1 |
|
四、单元测试
VirtualWalletTest
1 | public class VirtualWalletTest { |
运行结果
TransactionLogTest
1 | public class TransactionLogTest { |
运行结果
五、集成测试
使用apifox进行集成测试,注意测试的时候需要在在8080后面加入/elm
先在IDEA中启动好springboot项目