0%

elm项目新增电子钱包

“饿了吧“项目电子钱包实验报告

一、需求分析:增加电子钱包功能

1. 背景

饿了吧项目原先没有电子钱包功能,支付都是通过银行卡或第三方支付(如支付宝、微信支付)完成。这种方式的局限性在于系统无法实现充值返点、退款暂存、透支、收款等功能,因此增加一个电子钱包功能变得必要。大部分电商平台都有钱包功能用于管理平台内的资金,提高用户黏性,方便资金流动。此次新增电子钱包功能以提升系统的灵活性和功能的丰富度。

2. 功能需求

  • 充值和提现功能
    • 用户可以向钱包充值和从钱包中提现。
    • 为鼓励用户将资金存入平台,充值时应有一定奖励。例如,充值满1000元赠送100元。提现时,充值时所得的奖励部分需扣除,以防止利用奖励规则的“薅羊毛”行为。
    • 奖励和手续费规则应支持动态设置,以应对不同的市场情况。
  • 支付和转账功能
    • 用户可以使用钱包进行支付和向其他钱包转账。
    • 支付过程应保证交易的原子性,确保支付成功后资金准确从付款方钱包转入收款方钱包,避免因网络或系统问题造成资金丢失或不一致。
  • 余额查询和交易流水查询功能
    • 用户可以随时查询电子钱包的当前余额。
    • 用户可以查看电子钱包的所有交易流水,了解资金的流动情况。
  • 冻结和透支功能
    • 冻结功能:买家支付后,货款进入卖家账户但处于冻结状态,待买家确认收货后方可解冻,以保障买卖双方的权益。
    • 透支功能:为VIP用户提供一定的透支额度。如果用户未在规定时间内归还透支款项,则需支付一定利息,这类似于小额贷款或支付宝花呗的功能。

3. 系统设计和划分

  • 虚拟钱包系统:负责管理用户在平台内的虚拟账户。所有与虚拟资金相关的功能,如充值、提现、支付、转账、冻结、透支等,均由该系统处理。
  • 三方支付系统:负责与用户的银行账户或第三方支付平台交互。当用户进行充值或提现操作时,三方支付系统与银行账户进行交互,将资金转入或转出平台内的虚拟钱包。

通过这种系统的划分,可以有效解耦虚拟钱包和外部资金的交互,减少系统的耦合度,提升可维护性和安全性。虚拟钱包系统聚焦于平台内资金的管理,而三方支付系统则专注于与外部支付渠道的对接。

二、方案设计

1、总体设计方案

在“饿了吧”项目中增加电子钱包功能,旨在实现平台内的资金管理,包括充值、提现、支付、查询余额、查询交易流水等核心功能。钱包功能被拆分为虚拟钱包系统和第三方支付系统两个子系统。虚拟钱包系统主要负责用户资金的存储和管理,而第三方支付系统负责与外部银行账户或支付渠道的对接。为了更好地实现面向对象的封装和业务逻辑集中管理,本次设计采用了基于充血模型的领域驱动设计(DDD)方法,特别在虚拟钱包模块中应用了充血模型。

2、充血模型的应用

在本次方案中,虚拟钱包模块采用充血模型来管理钱包的业务逻辑和数据状态。与传统贫血模型相比,充血模型将数据与相关业务逻辑封装到同一个类中。这种设计符合面向对象编程的封装特性,能够提高代码的聚合度和内聚性,从而更好地保证业务逻辑的一致性。

具体地,在“饿了吧”项目中,VirtualWallet类被设计为充血模型:

  • VirtualWallet类不仅保存用户钱包的基本信息(如余额、冻结金额、透支额度等),还直接包含了充值、提现、转账、冻结资金、解冻资金等业务方法。
  • 例如,在用户充值的场景中,recharge方法被封装在VirtualWallet类中,业务逻辑包括增加余额、计算奖励金额并存入奖励余额字段,这些操作都集中在领域对象中完成,而不是分散到服务层中。

这种设计方式的优势在于:

  • 高内聚性:数据和操作紧密结合,减少了业务逻辑分散于多个类的情况,使代码结构更加简洁。
  • 清晰的职责划分:将钱包管理的核心逻辑放在领域对象中,服务层的职责被简化为调用领域对象的方法,起到协调控制的作用,而非直接处理业务逻辑。
  • 增强可维护性:充血模型使得业务逻辑的更新集中在一个地方,减少了代码维护的难度和可能出现的错误。

3、模块设计与接口方案

  1. VirtualWallet领域模型类
    • 封装钱包的核心数据和操作,包括余额、冻结金额、透支额度、奖励余额等字段,以及rechargewithdrawtransferTofreezeunfreeze等方法。
    • 通过充血模型,将余额管理、奖励计算、资金冻结等逻辑放入领域对象中,减少了代码分散的情况。
  2. 数据访问层(Mapper层)
    • 使用MyBatis作为ORM工具,通过注解的方式实现对数据库的访问。
    • 例如,VirtualWalletMapper用于从数据库中获取钱包信息、保存或更新钱包状态,TransactionLogMapper用于保存和查询交易日志。
    • 每个方法与数据库中的操作保持一致,以确保数据的完整性与一致性。
  3. 服务层(Service层)
    • VirtualWalletService接口定义了钱包的主要操作,如充值、提现、转账、冻结和解冻资金等,VirtualWalletServiceImpl实现了具体的逻辑。
    • 服务层的主要职责是协调领域模型对象,调用VirtualWallet的业务方法,处理操作的事务管理以及与数据访问层的交互。
  4. 控制器层(Controller层)
    • VirtualWalletController用于暴露API接口,供前端或其他服务调用。
    • 控制器层接收用户的请求,解析参数并调用服务层方法。控制器仅负责用户请求的处理和响应的生成,具体的业务逻辑则委托给服务层和领域模型对象处理。
    • 例如,充值接口通过调用walletService.recharge()方法完成,服务层再进一步调用VirtualWallet的充血模型方法实现核心业务逻辑。

4、接口实现与流程

本次钱包功能中实现的主要接口包括:

  1. 充值接口
    • 路径:POST /wallet/recharge
    • 参数:walletIdamountrewardRate
    • 流程:控制器层调用服务层的recharge方法,服务层获取钱包对象并调用VirtualWalletrecharge方法更新余额和奖励。
  2. 提现接口
    • 路径:POST /wallet/withdraw
    • 参数:walletIdamountfeeRate
    • 流程:调用服务层的withdraw方法,服务层从数据库获取钱包对象并调用withdraw方法处理扣款逻辑,并在完成后更新数据库。
  3. 转账接口
    • 路径:POST /wallet/transfer
    • 参数:fromWalletIdtoWalletIdamount
    • 流程:控制器接收参数,服务层获取两个钱包对象,通过调用VirtualWallettransferTo方法完成资金的转移。
  4. 交易日志查询接口
    • 路径:GET /wallet/{walletId}/transactions
    • 参数:walletId
    • 流程:服务层调用TransactionLogMapper查询指定钱包的所有交易日志,返回给调用方。

5、数据库的设计

根据需求我们可以设计如下数据库schemas

image-20241022015106352image-20241022015118504

三、代码实现

先根据数据库的设计编写出相应的sql语句,并插入几条测试数据

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
-- 钱包表 (wallet)
CREATE TABLE wallet (
wallet_id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(20) UNIQUE NOT NULL,
balance DECIMAL(15, 2) DEFAULT 0,
frozen_amount DECIMAL(15, 2) DEFAULT 0,
overdraft_limit DECIMAL(15, 2) DEFAULT 0,
reward_balance DECIMAL(15, 2) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(userId)
);


-- 交易流水表 (transaction_log)
CREATE TABLE transaction_log (
transaction_id INT PRIMARY KEY AUTO_INCREMENT,
wallet_id VARCHAR(36),
transaction_time DATETIME DEFAULT CURRENT_TIMESTAMP,
amount DECIMAL(15, 2) NOT NULL,
transaction_type VARCHAR(20) NOT NULL,
from_wallet_id VARCHAR(36),
to_wallet_id VARCHAR(36),
status VARCHAR(20) NOT NULL,
FOREIGN KEY (wallet_id) REFERENCES wallet(wallet_id)
);

-- 插入测试数据到 wallet 表中
INSERT INTO wallet (wallet_id, user_id, balance, frozen_amount, overdraft_limit, reward_balance, created_at, updated_at)
VALUES
('wallet_001', '18854553694', 1000.00, 0.00, 500.00, 0.00, NOW(), NOW()),
('wallet_002', '123', 2000.00, 0.00, 500.00, 0.00, NOW(), NOW()),
('wallet_003', '123456', 3000.00, 0.00, 800.00, 0.00, NOW(), NOW());

然后,根据需求,我们需要新增VirtualWallet类和TransactionLog类,并且在这两个类中实现各自的方法

VirtualWallet

VirtualWallet类的实体属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private String walletId;
private String userId;
private BigDecimal balance; // 可用余额
BigDecimal frozenAmount; // 冻结金额
private BigDecimal overdraftLimit; // 透支额度

public BigDecimal getFrozenAmount() {
return frozenAmount;
}

public void setFrozenAmount(BigDecimal frozenAmount) {
this.frozenAmount = frozenAmount;
}

BigDecimal rewardBalance; // 奖励余额

然后写出相应的构造函数和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
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
// 冻结资金
public void freeze(BigDecimal amount) throws Exception {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("冻结金额必须大于零");
}
if (this.balance.compareTo(amount) < 0) {
throw new Exception("余额不足以冻结");
}
this.frozenAmount = this.frozenAmount.add(amount);
this.balance = this.balance.subtract(amount);
}

// 解冻资金
public void unfreeze(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("解冻金额必须大于零");
}

// 打印日志查看 frozenAmount 和 amount 的值
System.out.println("Frozen Amount: " + this.frozenAmount);
System.out.println("Unfreeze Amount: " + amount);

if (this.frozenAmount.compareTo(amount) < 0) {
throw new IllegalArgumentException("解冻金额超过冻结金额");
}
this.frozenAmount = this.frozenAmount.subtract(amount);
this.balance = this.balance.add(amount);
}

然后写出相应的Controller层、Service层和ServiceImpl层,由于篇幅原因只给出一部分

VirtualWalletController

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
@RestController
@RequestMapping("/wallet")
public class VirtualWalletController {

@Autowired
private VirtualWalletService walletService;

// 充值接口
@PostMapping("/recharge")
public ResponseEntity<String> recharge(@RequestParam String walletId, @RequestParam BigDecimal amount, @RequestParam BigDecimal rewardRate) {
walletService.recharge(walletId, amount, rewardRate);
return ResponseEntity.ok("充值成功");
}

// 提现接口
@PostMapping("/withdraw")
public ResponseEntity<String> withdraw(@RequestParam String walletId, @RequestParam BigDecimal amount, @RequestParam BigDecimal feeRate) {
try {
walletService.withdraw(walletId, amount, feeRate);
return ResponseEntity.ok("提现成功");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}

.....

Service层

1
2
3
4
5
public interface VirtualWalletService {
void recharge(String walletId, BigDecimal amount, BigDecimal rewardRate);
void withdraw(String walletId, BigDecimal amount, BigDecimal feeRate) throws Exception;
void transfer(String fromWalletId, String toWalletId, BigDecimal amount) throws Exception;
.....

ServiceImpl层

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
@Service
public class VirtualWalletServiceImpl implements VirtualWalletService {

@Autowired
private VirtualWalletMapper walletMapper;

@Autowired
private TransactionLogMapper transactionLogMapper;

@Override
public void recharge(String walletId, BigDecimal amount, BigDecimal rewardRate) {
VirtualWallet wallet = walletMapper.findById(walletId);
wallet.recharge(amount, rewardRate);
walletMapper.update(wallet);
TransactionLog log = new TransactionLog(walletId, amount, "充值", "成功");
transactionLogMapper.save(log);
}

@Override
public void withdraw(String walletId, BigDecimal amount, BigDecimal feeRate) throws Exception {
VirtualWallet wallet = walletMapper.findById(walletId);
wallet.withdraw(amount, feeRate);
walletMapper.update(wallet);
TransactionLog log = new TransactionLog(walletId, amount, "提现", "成功");
transactionLogMapper.save(log);
}
....

最后完成VirtualWalletMapper层的撰写,注意,这里的实体类和数据库中的字段名不一致,应当用@Result来进行一一对应

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
35
36
37
@Service
public class VirtualWalletServiceImpl implements VirtualWalletService {

@Autowired
private VirtualWalletMapper walletMapper;

@Autowired
private TransactionLogMapper transactionLogMapper;

@Override
public void recharge(String walletId, BigDecimal amount, BigDecimal rewardRate) {
VirtualWallet wallet = walletMapper.findById(walletId);
wallet.recharge(amount, rewardRate);
walletMapper.update(wallet);
TransactionLog log = new TransactionLog(walletId, amount, "充值", "成功");
transactionLogMapper.save(log);
}

@Override
public void withdraw(String walletId, BigDecimal amount, BigDecimal feeRate) throws Exception {
VirtualWallet wallet = walletMapper.findById(walletId);
wallet.withdraw(amount, feeRate);
walletMapper.update(wallet);
TransactionLog log = new TransactionLog(walletId, amount, "提现", "成功");
transactionLogMapper.save(log);
}

@Override
public void transfer(String fromWalletId, String toWalletId, BigDecimal amount) throws Exception {
VirtualWallet fromWallet = walletMapper.findById(fromWalletId);
VirtualWallet toWallet = walletMapper.findById(toWalletId);
fromWallet.transferTo(toWallet, amount);
walletMapper.update(fromWallet);
walletMapper.update(toWallet);
TransactionLog log = new TransactionLog(fromWalletId, amount, "转账", "成功");
transactionLogMapper.save(log);
}

TransactionLog

TransactionLog实体类

1
2
3
4
5
6
7
8
private int transactionId; // 交易唯一标识
private String walletId; // 钱包ID
private LocalDateTime transactionTime; // 交易时间
private BigDecimal amount; // 交易金额
private String transactionType; // 交易类型(充值、提现、支付等)
private String fromWalletId; // 支付来源钱包ID(可为空)
private String toWalletId; // 支付目标钱包ID(可为空)
private String status; // 交易状态(成功、失败、处理中)

然后给出相应的构造函数、getter和setter方法,注意这里需要新增一个无参构造的方法,具体原因见

记Mybatis的坑,解决Error attempting to get column ‘name‘ from result set,Cannot determine value type from_org.springframework.dao.dataintegrityviolationexce-CSDN博客

然后要更新交易状态并获取交易详细信息的简化展示

1
2
3
4
5
6
7
8
9
10
11
12
// 更新交易状态
public void updateStatus(String newStatus) {
if (newStatus == null || newStatus.isEmpty()) {
throw new IllegalArgumentException("交易状态不能为空");
}
this.status = newStatus;
}

// 获取交易详细信息的简化展示
public String getTransactionSummary() {
return "Transaction " + transactionId + ": " + transactionType + " of " + amount + " at " + transactionTime;
}

TransactionLogMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Mapper
public interface TransactionLogMapper {

// 保存交易记录
@Insert("INSERT INTO transaction_log (wallet_id, transaction_time, amount, transaction_type, from_wallet_id, to_wallet_id, status) " +
"VALUES (#{walletId}, #{transactionTime}, #{amount}, #{transactionType}, #{fromWalletId}, #{toWalletId}, #{status})")
void save(TransactionLog transactionLog);

// 根据钱包ID查找所有交易记录
@Select("SELECT transaction_id, wallet_id, transaction_time, amount, transaction_type, from_wallet_id, to_wallet_id, status FROM transaction_log WHERE wallet_id = #{walletId}")
@Results({
@Result(property = "transactionId", column = "transaction_id"),
@Result(property = "walletId", column = "wallet_id"),
@Result(property = "transactionTime", column = "transaction_time"),
@Result(property = "amount", column = "amount"),
@Result(property = "transactionType", column = "transaction_type"),
@Result(property = "fromWalletId", column = "from_wallet_id"),
@Result(property = "toWalletId", column = "to_wallet_id"),
@Result(property = "status", column = "status")
})
List<TransactionLog> findByWalletId(@Param("walletId") String walletId);
}

四、单元测试

VirtualWalletTest

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class VirtualWalletTest {

private VirtualWallet wallet;
private VirtualWallet targetWallet;

@BeforeEach
void setUp() {
wallet = new VirtualWallet("wallet1", "user1", new BigDecimal("1000.00"), new BigDecimal("500.00"));
targetWallet = new VirtualWallet("wallet2", "user2", new BigDecimal("500.00"), new BigDecimal("300.00"));
}

@Test
void testRecharge() {
wallet.recharge(new BigDecimal("500.00"), new BigDecimal("0.1"));
assertEquals(new BigDecimal("1500.00"), wallet.getBalance()); // 余额增加
assertTrue(wallet.rewardBalance.compareTo(new BigDecimal("50.00")) == 0);// 奖励余额增加
}

@Test
void testRechargeNegativeAmount() {
assertThrows(IllegalArgumentException.class, () -> wallet.recharge(new BigDecimal("-100.00"), new BigDecimal("0.1")));
}

@Test
void testWithdraw() {
assertDoesNotThrow(() -> wallet.withdraw(new BigDecimal("200.00"), new BigDecimal("0.05")));
assertTrue(wallet.getBalance().compareTo(new BigDecimal("790.00")) == 0); // 余额减去 200 + 10(手续费)
}

@Test
void testWithdrawInsufficientBalance() {
assertThrows(Exception.class, () -> wallet.withdraw(new BigDecimal("2000.00"), new BigDecimal("0.05")));
}

@Test
void testTransferTo() throws Exception {
wallet.transferTo(targetWallet, new BigDecimal("300.00"));
assertTrue(wallet.getBalance().compareTo(new BigDecimal("700.00")) == 0); // 余额减去 300
assertTrue(targetWallet.getBalance().compareTo(new BigDecimal("800.00")) == 0); // 目标钱包增加 300
}

@Test
void testTransferToInsufficientBalance() {
assertThrows(Exception.class, () -> wallet.transferTo(targetWallet, new BigDecimal("2000.00")));
}

@Test
void testFreeze() throws Exception {
wallet.freeze(new BigDecimal("300.00"));
assertTrue(wallet.getBalance().compareTo(new BigDecimal("700.00")) == 0); // 冻结 300, 1000-300 = 700
assertTrue(wallet.frozenAmount.compareTo(new BigDecimal("300.00")) == 0); // 冻结金额增加 300
}

@Test
void testUnfreeze() throws Exception {
wallet.freeze(new BigDecimal("300.00"));
wallet.unfreeze(new BigDecimal("300.00"));
assertTrue(wallet.getBalance().compareTo(new BigDecimal("1000.00")) == 0); // 解冻后恢复到原余额
assertTrue(wallet.frozenAmount.compareTo(new BigDecimal("0.00")) == 0); // 冻结金额为 0
}

@Test
void testWithdrawWithInvalidAmount() {
assertThrows(IllegalArgumentException.class, () -> wallet.withdraw(new BigDecimal("-100.00"), new BigDecimal("0.1")));
}

@Test
void testFreezeWithInvalidAmount() {
assertThrows(IllegalArgumentException.class, () -> wallet.freeze(new BigDecimal("-100.00")));
}

@Test
void testUnfreezeWithInvalidAmount() {
assertThrows(IllegalArgumentException.class, () -> wallet.unfreeze(new BigDecimal("-100.00")));
}
}

运行结果 image-20241021235219505

TransactionLogTest

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
35
36
37
38
39
40
41
42
43
44
public class TransactionLogTest {

private TransactionLog transactionLog;

@BeforeEach
void setUp() {
transactionLog = new TransactionLog("wallet1", new BigDecimal("500.00"), "充值", "成功");
}

@Test
void testConstructor() {
assertNotNull(transactionLog);
assertEquals("wallet1", transactionLog.getWalletId());
assertEquals(new BigDecimal("500.00"), transactionLog.getAmount());
assertEquals("充值", transactionLog.getTransactionType());
assertEquals("成功", transactionLog.getStatus());
assertNotNull(transactionLog.getTransactionTime());
}

@Test
void testUpdateStatus() {
transactionLog.updateStatus("失败");
assertEquals("失败", transactionLog.getStatus());
}

@Test
void testUpdateStatusWithEmptyString() {
assertThrows(IllegalArgumentException.class, () -> transactionLog.updateStatus(""));
}

@Test
void testUpdateStatusWithNull() {
assertThrows(IllegalArgumentException.class, () -> transactionLog.updateStatus(null));
}

@Test
void testGetTransactionSummary() {
String summary = transactionLog.getTransactionSummary();
assertTrue(summary.contains("Transaction"));
assertTrue(summary.contains("充值"));
assertTrue(summary.contains("500.00"));
assertTrue(summary.contains(String.valueOf(transactionLog.getTransactionTime().getYear())));
}
}

运行结果

image-20241021235412096

五、集成测试

使用apifox进行集成测试,注意测试的时候需要在在8080后面加入/elm

先在IDEA中启动好springboot项目

image-20241022014546059

1、钱包充值

image-20241021235851395

2.钱包提现

image-20241022001841772

3.钱包转账

image-20241022001912365

4.冻结资金

image-20241022001938568

5.解冻资金

image-20241022002014950

6.查询余额接口

image-20241022002102031

7.获取交易日志接口

image-20241022002217781

-------------本文结束感谢您的阅读-------------