前言

电商系统的资金账户是核心业务模块,涉及用户余额、商家账户、平台资金等多个维度。合理的账户表设计和交易处理机制,不仅关系到系统的稳定性和安全性,更直接影响用户体验和业务发展。本文从数据库设计到业务实现,系统梳理企业级资金账户的完整解决方案。

一、资金账户架构设计

1.1 账户体系架构

1.2 账户类型设计

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
public enum AccountType {
/**
* 用户账户
*/
USER_ACCOUNT("USER", "用户账户"),

/**
* 商家账户
*/
MERCHANT_ACCOUNT("MERCHANT", "商家账户"),

/**
* 平台账户
*/
PLATFORM_ACCOUNT("PLATFORM", "平台账户"),

/**
* 保证金账户
*/
DEPOSIT_ACCOUNT("DEPOSIT", "保证金账户"),

/**
* 积分账户
*/
POINTS_ACCOUNT("POINTS", "积分账户"),

/**
* 优惠券账户
*/
COUPON_ACCOUNT("COUPON", "优惠券账户");

private final String code;
private final String description;

AccountType(String code, String description) {
this.code = code;
this.description = description;
}
}

二、数据库表设计

2.1 账户主表设计

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
-- 账户主表
CREATE TABLE `account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`account_type` varchar(20) NOT NULL COMMENT '账户类型',
`account_name` varchar(100) NOT NULL COMMENT '账户名称',
`balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '账户余额',
`frozen_amount` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '冻结金额',
`available_amount` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '可用余额',
`total_income` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '累计收入',
`total_expense` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '累计支出',
`currency` varchar(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '账户状态:1-正常,2-冻结,3-注销',
`risk_level` tinyint(4) NOT NULL DEFAULT '1' COMMENT '风险等级:1-低,2-中,3-高',
`daily_limit` decimal(15,2) DEFAULT NULL COMMENT '日限额',
`monthly_limit` decimal(15,2) DEFAULT NULL COMMENT '月限额',
`single_limit` decimal(15,2) DEFAULT NULL COMMENT '单笔限额',
`password_hash` varchar(128) DEFAULT NULL COMMENT '支付密码哈希',
`last_transaction_time` datetime DEFAULT NULL COMMENT '最后交易时间',
`version` int(11) NOT NULL DEFAULT '1' COMMENT '版本号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_account_no` (`account_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_account_type` (`account_type`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户主表';

2.2 交易记录表设计

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
-- 交易记录表
CREATE TABLE `transaction` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '交易ID',
`transaction_no` varchar(32) NOT NULL COMMENT '交易流水号',
`account_id` bigint(20) NOT NULL COMMENT '账户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`transaction_type` varchar(20) NOT NULL COMMENT '交易类型',
`transaction_subtype` varchar(20) DEFAULT NULL COMMENT '交易子类型',
`amount` decimal(15,2) NOT NULL COMMENT '交易金额',
`balance_before` decimal(15,2) NOT NULL COMMENT '交易前余额',
`balance_after` decimal(15,2) NOT NULL COMMENT '交易后余额',
`frozen_amount_before` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '交易前冻结金额',
`frozen_amount_after` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '交易后冻结金额',
`currency` varchar(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`direction` tinyint(4) NOT NULL COMMENT '资金方向:1-收入,2-支出',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '交易状态:1-处理中,2-成功,3-失败,4-取消',
`channel` varchar(20) DEFAULT NULL COMMENT '交易渠道',
`channel_transaction_no` varchar(64) DEFAULT NULL COMMENT '渠道交易号',
`related_transaction_no` varchar(32) DEFAULT NULL COMMENT '关联交易号',
`order_no` varchar(32) DEFAULT NULL COMMENT '订单号',
`description` varchar(500) DEFAULT NULL COMMENT '交易描述',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`fee` decimal(15,2) DEFAULT '0.00' COMMENT '手续费',
`actual_amount` decimal(15,2) DEFAULT NULL COMMENT '实际到账金额',
`risk_score` int(11) DEFAULT '0' COMMENT '风险评分',
`risk_reason` varchar(200) DEFAULT NULL COMMENT '风险原因',
`operator_id` bigint(20) DEFAULT NULL COMMENT '操作员ID',
`operator_name` varchar(50) DEFAULT NULL COMMENT '操作员姓名',
`client_ip` varchar(50) DEFAULT NULL COMMENT '客户端IP',
`user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_transaction_no` (`transaction_no`),
KEY `idx_account_id` (`account_id`),
KEY `idx_account_no` (`account_no`),
KEY `idx_transaction_type` (`transaction_type`),
KEY `idx_status` (`status`),
KEY `idx_channel_transaction_no` (`channel_transaction_no`),
KEY `idx_order_no` (`order_no`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易记录表';

2.3 资金流水表设计

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
-- 资金流水表
CREATE TABLE `account_flow` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '流水ID',
`flow_no` varchar(32) NOT NULL COMMENT '流水号',
`account_id` bigint(20) NOT NULL COMMENT '账户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`transaction_id` bigint(20) NOT NULL COMMENT '交易ID',
`transaction_no` varchar(32) NOT NULL COMMENT '交易流水号',
`flow_type` varchar(20) NOT NULL COMMENT '流水类型',
`amount` decimal(15,2) NOT NULL COMMENT '流水金额',
`balance_before` decimal(15,2) NOT NULL COMMENT '流水前余额',
`balance_after` decimal(15,2) NOT NULL COMMENT '流水后余额',
`currency` varchar(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`direction` tinyint(4) NOT NULL COMMENT '资金方向:1-收入,2-支出',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '流水状态:1-正常,2-冲正',
`description` varchar(500) DEFAULT NULL COMMENT '流水描述',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_flow_no` (`flow_no`),
KEY `idx_account_id` (`account_id`),
KEY `idx_account_no` (`account_no`),
KEY `idx_transaction_id` (`transaction_id`),
KEY `idx_transaction_no` (`transaction_no`),
KEY `idx_flow_type` (`flow_type`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水表';

2.4 账户冻结记录表设计

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
-- 账户冻结记录表
CREATE TABLE `account_freeze` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '冻结ID',
`freeze_no` varchar(32) NOT NULL COMMENT '冻结单号',
`account_id` bigint(20) NOT NULL COMMENT '账户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`freeze_type` varchar(20) NOT NULL COMMENT '冻结类型',
`freeze_amount` decimal(15,2) NOT NULL COMMENT '冻结金额',
`freeze_reason` varchar(500) NOT NULL COMMENT '冻结原因',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '冻结状态:1-冻结中,2-已解冻',
`operator_id` bigint(20) DEFAULT NULL COMMENT '操作员ID',
`operator_name` varchar(50) DEFAULT NULL COMMENT '操作员姓名',
`freeze_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '冻结时间',
`unfreeze_time` datetime DEFAULT NULL COMMENT '解冻时间',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_freeze_no` (`freeze_no`),
KEY `idx_account_id` (`account_id`),
KEY `idx_account_no` (`account_no`),
KEY `idx_freeze_type` (`freeze_type`),
KEY `idx_status` (`status`),
KEY `idx_freeze_time` (`freeze_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户冻结记录表';

三、核心业务实现

3.1 账户服务实现

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
@Service
@Transactional
@Slf4j
public class AccountService {

@Autowired
private AccountMapper accountMapper;

@Autowired
private TransactionMapper transactionMapper;

@Autowired
private AccountFlowMapper accountFlowMapper;

@Autowired
private AccountFreezeMapper accountFreezeMapper;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private RedissonClient redissonClient;

/**
* 创建账户
*/
public Account createAccount(CreateAccountRequest request) {
try {
// 1. 参数校验
validateCreateAccountRequest(request);

// 2. 生成账户号
String accountNo = generateAccountNo(request.getAccountType());

// 3. 创建账户
Account account = new Account();
account.setAccountNo(accountNo);
account.setUserId(request.getUserId());
account.setAccountType(request.getAccountType());
account.setAccountName(request.getAccountName());
account.setBalance(BigDecimal.ZERO);
account.setFrozenAmount(BigDecimal.ZERO);
account.setAvailableAmount(BigDecimal.ZERO);
account.setTotalIncome(BigDecimal.ZERO);
account.setTotalExpense(BigDecimal.ZERO);
account.setCurrency(request.getCurrency());
account.setStatus(AccountStatus.NORMAL);
account.setRiskLevel(RiskLevel.LOW);
account.setDailyLimit(request.getDailyLimit());
account.setMonthlyLimit(request.getMonthlyLimit());
account.setSingleLimit(request.getSingleLimit());
account.setVersion(1);
account.setCreateTime(LocalDateTime.now());
account.setUpdateTime(LocalDateTime.now());

// 4. 保存账户
accountMapper.insert(account);

// 5. 缓存账户信息
cacheAccountInfo(account);

log.info("创建账户成功: accountNo={}, userId={}", accountNo, request.getUserId());

return account;

} catch (Exception e) {
log.error("创建账户失败: request={}", request, e);
throw new BusinessException("创建账户失败: " + e.getMessage());
}
}

/**
* 账户充值
*/
public Transaction rechargeAccount(RechargeRequest request) {
String lockKey = "account:lock:" + request.getAccountNo();
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 1. 获取账户信息
Account account = getAccountByNo(request.getAccountNo());
if (account == null) {
throw new BusinessException("账户不存在: " + request.getAccountNo());
}

// 2. 检查账户状态
if (account.getStatus() != AccountStatus.NORMAL) {
throw new BusinessException("账户状态异常,无法充值");
}

// 3. 检查充值限额
checkRechargeLimit(account, request.getAmount());

// 4. 创建交易记录
Transaction transaction = createTransaction(account, request);

// 5. 更新账户余额
updateAccountBalance(account, request.getAmount(), TransactionDirection.INCOME);

// 6. 创建资金流水
createAccountFlow(account, transaction);

// 7. 更新账户缓存
cacheAccountInfo(account);

log.info("账户充值成功: accountNo={}, amount={}", request.getAccountNo(), request.getAmount());

return transaction;

} else {
throw new BusinessException("账户操作超时,请稍后再试");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}

/**
* 账户消费
*/
public Transaction consumeAccount(ConsumeRequest request) {
String lockKey = "account:lock:" + request.getAccountNo();
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 1. 获取账户信息
Account account = getAccountByNo(request.getAccountNo());
if (account == null) {
throw new BusinessException("账户不存在: " + request.getAccountNo());
}

// 2. 检查账户状态
if (account.getStatus() != AccountStatus.NORMAL) {
throw new BusinessException("账户状态异常,无法消费");
}

// 3. 检查余额是否充足
if (account.getAvailableAmount().compareTo(request.getAmount()) < 0) {
throw new BusinessException("账户余额不足");
}

// 4. 检查消费限额
checkConsumeLimit(account, request.getAmount());

// 5. 创建交易记录
Transaction transaction = createTransaction(account, request);

// 6. 更新账户余额
updateAccountBalance(account, request.getAmount(), TransactionDirection.EXPENSE);

// 7. 创建资金流水
createAccountFlow(account, transaction);

// 8. 更新账户缓存
cacheAccountInfo(account);

log.info("账户消费成功: accountNo={}, amount={}", request.getAccountNo(), request.getAmount());

return transaction;

} else {
throw new BusinessException("账户操作超时,请稍后再试");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}

/**
* 账户冻结
*/
public AccountFreeze freezeAccount(FreezeAccountRequest request) {
try {
// 1. 获取账户信息
Account account = getAccountByNo(request.getAccountNo());
if (account == null) {
throw new BusinessException("账户不存在: " + request.getAccountNo());
}

// 2. 检查冻结金额
if (account.getAvailableAmount().compareTo(request.getFreezeAmount()) < 0) {
throw new BusinessException("可用余额不足,无法冻结");
}

// 3. 创建冻结记录
AccountFreeze accountFreeze = new AccountFreeze();
accountFreeze.setFreezeNo(generateFreezeNo());
accountFreeze.setAccountId(account.getId());
accountFreeze.setAccountNo(account.getAccountNo());
accountFreeze.setFreezeType(request.getFreezeType());
accountFreeze.setFreezeAmount(request.getFreezeAmount());
accountFreeze.setFreezeReason(request.getFreezeReason());
accountFreeze.setStatus(FreezeStatus.FREEZING);
accountFreeze.setOperatorId(request.getOperatorId());
accountFreeze.setOperatorName(request.getOperatorName());
accountFreeze.setFreezeTime(LocalDateTime.now());
accountFreeze.setExpireTime(request.getExpireTime());
accountFreeze.setCreateTime(LocalDateTime.now());
accountFreeze.setUpdateTime(LocalDateTime.now());

// 4. 保存冻结记录
accountFreezeMapper.insert(accountFreeze);

// 5. 更新账户冻结金额
account.setFrozenAmount(account.getFrozenAmount().add(request.getFreezeAmount()));
account.setAvailableAmount(account.getAvailableAmount().subtract(request.getFreezeAmount()));
account.setUpdateTime(LocalDateTime.now());
accountMapper.updateById(account);

// 6. 更新账户缓存
cacheAccountInfo(account);

log.info("账户冻结成功: accountNo={}, freezeAmount={}", request.getAccountNo(), request.getFreezeAmount());

return accountFreeze;

} catch (Exception e) {
log.error("账户冻结失败: request={}", request, e);
throw new BusinessException("账户冻结失败: " + e.getMessage());
}
}

/**
* 账户解冻
*/
public void unfreezeAccount(String freezeNo) {
try {
// 1. 获取冻结记录
AccountFreeze accountFreeze = accountFreezeMapper.findByFreezeNo(freezeNo);
if (accountFreeze == null) {
throw new BusinessException("冻结记录不存在: " + freezeNo);
}

if (accountFreeze.getStatus() != FreezeStatus.FREEZING) {
throw new BusinessException("冻结记录状态异常");
}

// 2. 获取账户信息
Account account = accountMapper.selectById(accountFreeze.getAccountId());
if (account == null) {
throw new BusinessException("账户不存在");
}

// 3. 更新冻结记录
accountFreeze.setStatus(FreezeStatus.UNFROZEN);
accountFreeze.setUnfreezeTime(LocalDateTime.now());
accountFreeze.setUpdateTime(LocalDateTime.now());
accountFreezeMapper.updateById(accountFreeze);

// 4. 更新账户冻结金额
account.setFrozenAmount(account.getFrozenAmount().subtract(accountFreeze.getFreezeAmount()));
account.setAvailableAmount(account.getAvailableAmount().add(accountFreeze.getFreezeAmount()));
account.setUpdateTime(LocalDateTime.now());
accountMapper.updateById(account);

// 5. 更新账户缓存
cacheAccountInfo(account);

log.info("账户解冻成功: freezeNo={}, accountNo={}", freezeNo, account.getAccountNo());

} catch (Exception e) {
log.error("账户解冻失败: freezeNo={}", freezeNo, e);
throw new BusinessException("账户解冻失败: " + e.getMessage());
}
}

/**
* 获取账户信息
*/
public Account getAccountByNo(String accountNo) {
try {
// 1. 从缓存获取
String cacheKey = "account:info:" + accountNo;
Account cachedAccount = (Account) redisTemplate.opsForValue().get(cacheKey);
if (cachedAccount != null) {
return cachedAccount;
}

// 2. 从数据库查询
Account account = accountMapper.findByAccountNo(accountNo);
if (account == null) {
throw new BusinessException("账户不存在: " + accountNo);
}

// 3. 缓存结果
redisTemplate.opsForValue().set(cacheKey, account, Duration.ofMinutes(30));

return account;

} catch (Exception e) {
log.error("获取账户信息失败: accountNo={}", accountNo, e);
throw new BusinessException("获取账户信息失败: " + e.getMessage());
}
}

/**
* 更新账户余额
*/
private void updateAccountBalance(Account account, BigDecimal amount, TransactionDirection direction) {
if (direction == TransactionDirection.INCOME) {
// 收入
account.setBalance(account.getBalance().add(amount));
account.setAvailableAmount(account.getAvailableAmount().add(amount));
account.setTotalIncome(account.getTotalIncome().add(amount));
} else {
// 支出
account.setBalance(account.getBalance().subtract(amount));
account.setAvailableAmount(account.getAvailableAmount().subtract(amount));
account.setTotalExpense(account.getTotalExpense().add(amount));
}

account.setLastTransactionTime(LocalDateTime.now());
account.setUpdateTime(LocalDateTime.now());
accountMapper.updateById(account);
}

/**
* 创建交易记录
*/
private Transaction createTransaction(Account account, Object request) {
Transaction transaction = new Transaction();
transaction.setTransactionNo(generateTransactionNo());
transaction.setAccountId(account.getId());
transaction.setAccountNo(account.getAccountNo());
transaction.setBalanceBefore(account.getBalance());
transaction.setFrozenAmountBefore(account.getFrozenAmount());
transaction.setCurrency(account.getCurrency());
transaction.setStatus(TransactionStatus.PROCESSING);
transaction.setCreateTime(LocalDateTime.now());
transaction.setUpdateTime(LocalDateTime.now());

// 根据请求类型设置具体字段
if (request instanceof RechargeRequest) {
RechargeRequest rechargeRequest = (RechargeRequest) request;
transaction.setTransactionType(TransactionType.RECHARGE);
transaction.setAmount(rechargeRequest.getAmount());
transaction.setDirection(TransactionDirection.INCOME);
transaction.setChannel(rechargeRequest.getChannel());
transaction.setChannelTransactionNo(rechargeRequest.getChannelTransactionNo());
transaction.setDescription("账户充值");
} else if (request instanceof ConsumeRequest) {
ConsumeRequest consumeRequest = (ConsumeRequest) request;
transaction.setTransactionType(TransactionType.CONSUME);
transaction.setAmount(consumeRequest.getAmount());
transaction.setDirection(TransactionDirection.EXPENSE);
transaction.setOrderNo(consumeRequest.getOrderNo());
transaction.setDescription("账户消费");
}

transactionMapper.insert(transaction);
return transaction;
}

/**
* 创建资金流水
*/
private void createAccountFlow(Account account, Transaction transaction) {
AccountFlow accountFlow = new AccountFlow();
accountFlow.setFlowNo(generateFlowNo());
accountFlow.setAccountId(account.getId());
accountFlow.setAccountNo(account.getAccountNo());
accountFlow.setTransactionId(transaction.getId());
accountFlow.setTransactionNo(transaction.getTransactionNo());
accountFlow.setFlowType(transaction.getTransactionType());
accountFlow.setAmount(transaction.getAmount());
accountFlow.setBalanceBefore(transaction.getBalanceBefore());
accountFlow.setBalanceAfter(account.getBalance());
accountFlow.setCurrency(account.getCurrency());
accountFlow.setDirection(transaction.getDirection());
accountFlow.setStatus(FlowStatus.NORMAL);
accountFlow.setDescription(transaction.getDescription());
accountFlow.setCreateTime(LocalDateTime.now());
accountFlow.setUpdateTime(LocalDateTime.now());

accountFlowMapper.insert(accountFlow);
}

/**
* 缓存账户信息
*/
private void cacheAccountInfo(Account account) {
try {
String cacheKey = "account:info:" + account.getAccountNo();
redisTemplate.opsForValue().set(cacheKey, account, Duration.ofMinutes(30));
} catch (Exception e) {
log.warn("缓存账户信息失败: accountNo={}", account.getAccountNo(), e);
}
}

/**
* 生成账户号
*/
private String generateAccountNo(AccountType accountType) {
String prefix = accountType.getCode();
String timestamp = String.valueOf(System.currentTimeMillis());
String random = String.valueOf(RandomUtils.nextInt(1000, 9999));
return prefix + timestamp + random;
}

/**
* 生成交易流水号
*/
private String generateTransactionNo() {
return "TXN" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
}

/**
* 生成流水号
*/
private String generateFlowNo() {
return "FLOW" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
}

/**
* 生成冻结单号
*/
private String generateFreezeNo() {
return "FREEZE" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
}
}

3.2 风控系统实现

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
@Service
@Slf4j
public class AccountRiskControlService {

@Autowired
private AccountMapper accountMapper;

@Autowired
private TransactionMapper transactionMapper;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 交易风控检查
*/
public RiskCheckResult checkTransactionRisk(TransactionRiskRequest request) {
try {
RiskCheckResult result = new RiskCheckResult();
result.setPass(true);
result.setRiskScore(0);
result.setRiskReasons(new ArrayList<>());

// 1. 基础风控检查
checkBasicRisk(request, result);

// 2. 频率风控检查
checkFrequencyRisk(request, result);

// 3. 金额风控检查
checkAmountRisk(request, result);

// 4. 行为风控检查
checkBehaviorRisk(request, result);

// 5. 综合评分
calculateRiskScore(result);

return result;

} catch (Exception e) {
log.error("交易风控检查失败: request={}", request, e);
throw new BusinessException("风控检查失败: " + e.getMessage());
}
}

/**
* 基础风控检查
*/
private void checkBasicRisk(TransactionRiskRequest request, RiskCheckResult result) {
// 1. 检查账户状态
Account account = accountMapper.findByAccountNo(request.getAccountNo());
if (account == null) {
result.setPass(false);
result.getRiskReasons().add("账户不存在");
return;
}

if (account.getStatus() != AccountStatus.NORMAL) {
result.setPass(false);
result.getRiskReasons().add("账户状态异常");
return;
}

// 2. 检查风险等级
if (account.getRiskLevel() == RiskLevel.HIGH) {
result.setRiskScore(result.getRiskScore() + 30);
result.getRiskReasons().add("账户风险等级高");
}

// 3. 检查余额
if (request.getAmount().compareTo(account.getAvailableAmount()) > 0) {
result.setPass(false);
result.getRiskReasons().add("账户余额不足");
return;
}
}

/**
* 频率风控检查
*/
private void checkFrequencyRisk(TransactionRiskRequest request, RiskCheckResult result) {
String accountNo = request.getAccountNo();

// 1. 检查日交易次数
int dailyCount = getDailyTransactionCount(accountNo);
if (dailyCount > 100) {
result.setRiskScore(result.getRiskScore() + 20);
result.getRiskReasons().add("日交易次数过多");
}

// 2. 检查小时交易次数
int hourlyCount = getHourlyTransactionCount(accountNo);
if (hourlyCount > 20) {
result.setRiskScore(result.getRiskScore() + 30);
result.getRiskReasons().add("小时交易次数过多");
}

// 3. 检查连续失败次数
int consecutiveFailures = getConsecutiveFailures(accountNo);
if (consecutiveFailures > 5) {
result.setRiskScore(result.getRiskScore() + 40);
result.getRiskReasons().add("连续失败次数过多");
}
}

/**
* 金额风控检查
*/
private void checkAmountRisk(TransactionRiskRequest request, RiskCheckResult result) {
String accountNo = request.getAccountNo();
BigDecimal amount = request.getAmount();

// 1. 检查单笔限额
Account account = accountMapper.findByAccountNo(accountNo);
if (account.getSingleLimit() != null && amount.compareTo(account.getSingleLimit()) > 0) {
result.setPass(false);
result.getRiskReasons().add("超过单笔限额");
return;
}

// 2. 检查日限额
BigDecimal dailyAmount = getDailyTransactionAmount(accountNo);
if (account.getDailyLimit() != null && dailyAmount.add(amount).compareTo(account.getDailyLimit()) > 0) {
result.setPass(false);
result.getRiskReasons().add("超过日限额");
return;
}

// 3. 检查月限额
BigDecimal monthlyAmount = getMonthlyTransactionAmount(accountNo);
if (account.getMonthlyLimit() != null && monthlyAmount.add(amount).compareTo(account.getMonthlyLimit()) > 0) {
result.setPass(false);
result.getRiskReasons().add("超过月限额");
return;
}

// 4. 检查大额交易
if (amount.compareTo(new BigDecimal("10000")) > 0) {
result.setRiskScore(result.getRiskScore() + 20);
result.getRiskReasons().add("大额交易");
}
}

/**
* 行为风控检查
*/
private void checkBehaviorRisk(TransactionRiskRequest request, RiskCheckResult result) {
String accountNo = request.getAccountNo();

// 1. 检查异常时间交易
LocalTime currentTime = LocalTime.now();
if (currentTime.isBefore(LocalTime.of(6, 0)) || currentTime.isAfter(LocalTime.of(23, 0))) {
result.setRiskScore(result.getRiskScore() + 10);
result.getRiskReasons().add("异常时间交易");
}

// 2. 检查异地交易
if (isAbnormalLocation(request.getClientIp(), accountNo)) {
result.setRiskScore(result.getRiskScore() + 30);
result.getRiskReasons().add("异地交易");
}

// 3. 检查设备异常
if (isAbnormalDevice(request.getUserAgent(), accountNo)) {
result.setRiskScore(result.getRiskScore() + 20);
result.getRiskReasons().add("设备异常");
}
}

/**
* 计算风险评分
*/
private void calculateRiskScore(RiskCheckResult result) {
if (result.getRiskScore() >= 80) {
result.setPass(false);
result.setRiskLevel(RiskLevel.HIGH);
} else if (result.getRiskScore() >= 50) {
result.setRiskLevel(RiskLevel.MEDIUM);
} else {
result.setRiskLevel(RiskLevel.LOW);
}
}

/**
* 获取日交易次数
*/
private int getDailyTransactionCount(String accountNo) {
String key = "risk:daily:count:" + accountNo + ":" + LocalDate.now().toString();
Object count = redisTemplate.opsForValue().get(key);
return count != null ? (Integer) count : 0;
}

/**
* 获取小时交易次数
*/
private int getHourlyTransactionCount(String accountNo) {
String key = "risk:hourly:count:" + accountNo + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
Object count = redisTemplate.opsForValue().get(key);
return count != null ? (Integer) count : 0;
}

/**
* 获取连续失败次数
*/
private int getConsecutiveFailures(String accountNo) {
String key = "risk:failures:" + accountNo;
Object count = redisTemplate.opsForValue().get(key);
return count != null ? (Integer) count : 0;
}

/**
* 获取日交易金额
*/
private BigDecimal getDailyTransactionAmount(String accountNo) {
String key = "risk:daily:amount:" + accountNo + ":" + LocalDate.now().toString();
Object amount = redisTemplate.opsForValue().get(key);
return amount != null ? (BigDecimal) amount : BigDecimal.ZERO;
}

/**
* 获取月交易金额
*/
private BigDecimal getMonthlyTransactionAmount(String accountNo) {
String key = "risk:monthly:amount:" + accountNo + ":" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
Object amount = redisTemplate.opsForValue().get(key);
return amount != null ? (BigDecimal) amount : BigDecimal.ZERO;
}

/**
* 检查是否异地交易
*/
private boolean isAbnormalLocation(String clientIp, String accountNo) {
// 实现异地交易检查逻辑
// 这里可以集成IP地理位置服务
return false;
}

/**
* 检查是否设备异常
*/
private boolean isAbnormalDevice(String userAgent, String accountNo) {
// 实现设备异常检查逻辑
// 这里可以分析用户代理字符串
return false;
}
}

3.3 对账系统实现

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@Service
@Slf4j
public class AccountReconciliationService {

@Autowired
private AccountMapper accountMapper;

@Autowired
private TransactionMapper transactionMapper;

@Autowired
private AccountFlowMapper accountFlowMapper;

/**
* 日终对账
*/
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void dailyReconciliation() {
try {
LocalDate reconcileDate = LocalDate.now().minusDays(1);
log.info("开始日终对账: date={}", reconcileDate);

// 1. 获取所有账户
List<Account> accounts = accountMapper.selectAll();

for (Account account : accounts) {
try {
// 2. 对账单个账户
reconcileAccount(account, reconcileDate);
} catch (Exception e) {
log.error("账户对账失败: accountNo={}, date={}", account.getAccountNo(), reconcileDate, e);
}
}

log.info("日终对账完成: date={}", reconcileDate);

} catch (Exception e) {
log.error("日终对账异常", e);
}
}

/**
* 对账单个账户
*/
public ReconciliationResult reconcileAccount(Account account, LocalDate reconcileDate) {
try {
ReconciliationResult result = new ReconciliationResult();
result.setAccountNo(account.getAccountNo());
result.setReconcileDate(reconcileDate);
result.setReconcileTime(LocalDateTime.now());

// 1. 计算理论余额
BigDecimal theoreticalBalance = calculateTheoreticalBalance(account, reconcileDate);

// 2. 获取实际余额
BigDecimal actualBalance = account.getBalance();

// 3. 计算差异
BigDecimal difference = actualBalance.subtract(theoreticalBalance);
result.setTheoreticalBalance(theoreticalBalance);
result.setActualBalance(actualBalance);
result.setDifference(difference);

// 4. 检查是否平衡
if (difference.abs().compareTo(new BigDecimal("0.01")) <= 0) {
result.setBalanced(true);
result.setStatus(ReconciliationStatus.BALANCED);
} else {
result.setBalanced(false);
result.setStatus(ReconciliationStatus.UNBALANCED);

// 5. 记录不平衡原因
analyzeUnbalanceReason(account, reconcileDate, result);
}

// 6. 保存对账结果
saveReconciliationResult(result);

log.info("账户对账完成: accountNo={}, balanced={}, difference={}",
account.getAccountNo(), result.isBalanced(), difference);

return result;

} catch (Exception e) {
log.error("账户对账失败: accountNo={}, date={}", account.getAccountNo(), reconcileDate, e);
throw new BusinessException("账户对账失败: " + e.getMessage());
}
}

/**
* 计算理论余额
*/
private BigDecimal calculateTheoreticalBalance(Account account, LocalDate reconcileDate) {
// 1. 获取对账日期的所有流水
List<AccountFlow> flows = accountFlowMapper.findByAccountAndDate(account.getId(), reconcileDate);

// 2. 计算理论余额
BigDecimal theoreticalBalance = account.getBalance();

for (AccountFlow flow : flows) {
if (flow.getDirection() == TransactionDirection.INCOME) {
theoreticalBalance = theoreticalBalance.add(flow.getAmount());
} else {
theoreticalBalance = theoreticalBalance.subtract(flow.getAmount());
}
}

return theoreticalBalance;
}

/**
* 分析不平衡原因
*/
private void analyzeUnbalanceReason(Account account, LocalDate reconcileDate, ReconciliationResult result) {
List<String> reasons = new ArrayList<>();

// 1. 检查是否有未处理的交易
List<Transaction> pendingTransactions = transactionMapper.findPendingByAccountAndDate(account.getId(), reconcileDate);
if (!pendingTransactions.isEmpty()) {
reasons.add("存在未处理的交易: " + pendingTransactions.size() + "笔");
}

// 2. 检查是否有重复的流水
List<AccountFlow> duplicateFlows = findDuplicateFlows(account.getId(), reconcileDate);
if (!duplicateFlows.isEmpty()) {
reasons.add("存在重复流水: " + duplicateFlows.size() + "笔");
}

// 3. 检查是否有缺失的流水
List<Transaction> missingFlows = findMissingFlows(account.getId(), reconcileDate);
if (!missingFlows.isEmpty()) {
reasons.add("存在缺失流水: " + missingFlows.size() + "笔");
}

result.setUnbalanceReasons(reasons);
}

/**
* 查找重复流水
*/
private List<AccountFlow> findDuplicateFlows(Long accountId, LocalDate reconcileDate) {
// 实现查找重复流水的逻辑
return new ArrayList<>();
}

/**
* 查找缺失流水
*/
private List<Transaction> findMissingFlows(Long accountId, LocalDate reconcileDate) {
// 实现查找缺失流水的逻辑
return new ArrayList<>();
}

/**
* 保存对账结果
*/
private void saveReconciliationResult(ReconciliationResult result) {
// 实现保存对账结果的逻辑
// 这里可以保存到数据库或发送到监控系统
}

/**
* 实时对账
*/
public void realTimeReconciliation(String accountNo, String transactionNo) {
try {
// 1. 获取账户信息
Account account = accountMapper.findByAccountNo(accountNo);
if (account == null) {
throw new BusinessException("账户不存在: " + accountNo);
}

// 2. 获取交易信息
Transaction transaction = transactionMapper.findByTransactionNo(transactionNo);
if (transaction == null) {
throw new BusinessException("交易不存在: " + transactionNo);
}

// 3. 获取流水信息
List<AccountFlow> flows = accountFlowMapper.findByTransactionId(transaction.getId());

// 4. 验证流水一致性
validateFlowConsistency(account, transaction, flows);

log.info("实时对账完成: accountNo={}, transactionNo={}", accountNo, transactionNo);

} catch (Exception e) {
log.error("实时对账失败: accountNo={}, transactionNo={}", accountNo, transactionNo, e);
throw new BusinessException("实时对账失败: " + e.getMessage());
}
}

/**
* 验证流水一致性
*/
private void validateFlowConsistency(Account account, Transaction transaction, List<AccountFlow> flows) {
// 1. 检查流水数量
if (flows.isEmpty()) {
throw new BusinessException("交易缺少流水记录");
}

// 2. 检查流水金额
BigDecimal totalFlowAmount = flows.stream()
.map(AccountFlow::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);

if (totalFlowAmount.compareTo(transaction.getAmount()) != 0) {
throw new BusinessException("流水金额与交易金额不一致");
}

// 3. 检查余额变化
for (AccountFlow flow : flows) {
BigDecimal expectedBalanceAfter = flow.getBalanceBefore()
.add(flow.getDirection() == TransactionDirection.INCOME ? flow.getAmount() : flow.getAmount().negate());

if (flow.getBalanceAfter().compareTo(expectedBalanceAfter) != 0) {
throw new BusinessException("流水余额计算错误");
}
}
}
}

四、监控与告警

4.1 账户监控服务

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@Service
@Slf4j
public class AccountMonitoringService {

@Autowired
private MeterRegistry meterRegistry;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 记录账户操作指标
*/
public void recordAccountOperation(String accountNo, String operation, long duration) {
try {
// 1. 记录操作次数
Counter.builder("account.operation.count")
.tag("account_no", accountNo)
.tag("operation", operation)
.register(meterRegistry)
.increment();

// 2. 记录操作耗时
Timer.builder("account.operation.duration")
.tag("account_no", accountNo)
.tag("operation", operation)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);

// 3. 记录到Redis
String key = "account:metrics:" + accountNo + ":" + LocalDate.now().toString();
redisTemplate.opsForHash().increment(key, operation + ":count", 1);
redisTemplate.opsForHash().increment(key, operation + ":duration", duration);
redisTemplate.expire(key, Duration.ofDays(7));

} catch (Exception e) {
log.error("记录账户操作指标失败: accountNo={}, operation={}", accountNo, operation, e);
}
}

/**
* 记录账户余额指标
*/
public void recordAccountBalance(String accountNo, BigDecimal balance) {
try {
// 1. 记录余额指标
Gauge.builder("account.balance")
.tag("account_no", accountNo)
.register(meterRegistry, balance, BigDecimal::doubleValue);

// 2. 记录到Redis
String key = "account:balance:" + accountNo;
redisTemplate.opsForValue().set(key, balance, Duration.ofHours(1));

} catch (Exception e) {
log.error("记录账户余额指标失败: accountNo={}", accountNo, e);
}
}

/**
* 检查账户异常
*/
@Scheduled(fixedDelay = 300000) // 5分钟检查一次
public void checkAccountAnomalies() {
try {
// 1. 获取所有账户
List<Account> accounts = accountMapper.selectAll();

for (Account account : accounts) {
// 2. 检查余额异常
checkBalanceAnomaly(account);

// 3. 检查交易异常
checkTransactionAnomaly(account);

// 4. 检查风险异常
checkRiskAnomaly(account);
}

} catch (Exception e) {
log.error("检查账户异常失败", e);
}
}

/**
* 检查余额异常
*/
private void checkBalanceAnomaly(Account account) {
// 1. 检查余额为负数
if (account.getBalance().compareTo(BigDecimal.ZERO) < 0) {
log.warn("账户余额为负数: accountNo={}, balance={}", account.getAccountNo(), account.getBalance());
sendBalanceAlert(account, "NEGATIVE_BALANCE", account.getBalance());
}

// 2. 检查余额异常增长
BigDecimal previousBalance = getPreviousBalance(account.getAccountNo());
if (previousBalance != null) {
BigDecimal growth = account.getBalance().subtract(previousBalance);
if (growth.compareTo(new BigDecimal("100000")) > 0) {
log.warn("账户余额异常增长: accountNo={}, growth={}", account.getAccountNo(), growth);
sendBalanceAlert(account, "ABNORMAL_GROWTH", growth);
}
}
}

/**
* 检查交易异常
*/
private void checkTransactionAnomaly(Account account) {
// 1. 检查交易频率
int hourlyCount = getHourlyTransactionCount(account.getAccountNo());
if (hourlyCount > 50) {
log.warn("账户交易频率过高: accountNo={}, count={}", account.getAccountNo(), hourlyCount);
sendTransactionAlert(account, "HIGH_FREQUENCY", hourlyCount);
}

// 2. 检查大额交易
BigDecimal maxAmount = getMaxTransactionAmount(account.getAccountNo());
if (maxAmount.compareTo(new BigDecimal("50000")) > 0) {
log.warn("账户存在大额交易: accountNo={}, amount={}", account.getAccountNo(), maxAmount);
sendTransactionAlert(account, "LARGE_AMOUNT", maxAmount);
}
}

/**
* 检查风险异常
*/
private void checkRiskAnomaly(Account account) {
// 1. 检查风险等级
if (account.getRiskLevel() == RiskLevel.HIGH) {
log.warn("账户风险等级高: accountNo={}", account.getAccountNo());
sendRiskAlert(account, "HIGH_RISK", account.getRiskLevel());
}

// 2. 检查连续失败
int consecutiveFailures = getConsecutiveFailures(account.getAccountNo());
if (consecutiveFailures > 10) {
log.warn("账户连续失败次数过多: accountNo={}, failures={}", account.getAccountNo(), consecutiveFailures);
sendRiskAlert(account, "CONSECUTIVE_FAILURES", consecutiveFailures);
}
}

/**
* 发送余额告警
*/
private void sendBalanceAlert(Account account, String alertType, BigDecimal value) {
try {
BalanceAlert alert = new BalanceAlert();
alert.setAccountNo(account.getAccountNo());
alert.setAlertType(alertType);
alert.setValue(value);
alert.setTimestamp(LocalDateTime.now());

// 发送告警通知
log.info("发送余额告警: {}", alert);

} catch (Exception e) {
log.error("发送余额告警失败: accountNo={}, alertType={}", account.getAccountNo(), alertType, e);
}
}

/**
* 发送交易告警
*/
private void sendTransactionAlert(Account account, String alertType, Object value) {
try {
TransactionAlert alert = new TransactionAlert();
alert.setAccountNo(account.getAccountNo());
alert.setAlertType(alertType);
alert.setValue(value);
alert.setTimestamp(LocalDateTime.now());

// 发送告警通知
log.info("发送交易告警: {}", alert);

} catch (Exception e) {
log.error("发送交易告警失败: accountNo={}, alertType={}", account.getAccountNo(), alertType, e);
}
}

/**
* 发送风险告警
*/
private void sendRiskAlert(Account account, String alertType, Object value) {
try {
RiskAlert alert = new RiskAlert();
alert.setAccountNo(account.getAccountNo());
alert.setAlertType(alertType);
alert.setValue(value);
alert.setTimestamp(LocalDateTime.now());

// 发送告警通知
log.info("发送风险告警: {}", alert);

} catch (Exception e) {
log.error("发送风险告警失败: accountNo={}, alertType={}", account.getAccountNo(), alertType, e);
}
}

/**
* 获取前一次余额
*/
private BigDecimal getPreviousBalance(String accountNo) {
String key = "account:balance:" + accountNo;
Object balance = redisTemplate.opsForValue().get(key);
return balance != null ? (BigDecimal) balance : null;
}

/**
* 获取小时交易次数
*/
private int getHourlyTransactionCount(String accountNo) {
String key = "account:hourly:count:" + accountNo + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
Object count = redisTemplate.opsForValue().get(key);
return count != null ? (Integer) count : 0;
}

/**
* 获取最大交易金额
*/
private BigDecimal getMaxTransactionAmount(String accountNo) {
String key = "account:max:amount:" + accountNo + ":" + LocalDate.now().toString();
Object amount = redisTemplate.opsForValue().get(key);
return amount != null ? (BigDecimal) amount : BigDecimal.ZERO;
}

/**
* 获取连续失败次数
*/
private int getConsecutiveFailures(String accountNo) {
String key = "account:failures:" + accountNo;
Object count = redisTemplate.opsForValue().get(key);
return count != null ? (Integer) count : 0;
}
}

五、最佳实践总结

5.1 数据库设计最佳实践

  1. 表结构设计:合理的字段设计和索引优化
  2. 数据一致性:使用事务和锁机制保证数据一致性
  3. 性能优化:合理的分页查询和缓存策略
  4. 扩展性:预留扩展字段和分表策略

5.2 业务实现最佳实践

  1. 原子性操作:使用分布式锁保证操作的原子性
  2. 异常处理:完善的异常处理和回滚机制
  3. 幂等性:保证操作的幂等性,避免重复处理
  4. 监控告警:完善的监控指标和告警机制

5.3 安全防护最佳实践

  1. 权限控制:细粒度的权限控制机制
  2. 风控系统:多层次的风控检查
  3. 审计日志:完整的操作审计日志
  4. 数据加密:敏感数据的加密存储

六、总结

电商系统资金账户表设计及应用是电商平台的核心模块,通过合理的数据库设计、完善的业务实现和有效的安全防护,可以构建一个稳定、安全、高效的资金管理系统。

关键要点:

  1. 数据库设计:合理的表结构和索引设计
  2. 业务实现:完善的账户管理和交易处理
  3. 安全防护:多层次的风控和监控机制
  4. 性能优化:有效的缓存和查询优化
  5. 监控告警:完善的监控指标和告警系统

通过本文的实践指导,读者可以快速搭建企业级的资金账户系统,为电商平台提供强有力的技术支撑。