用户账户创建充值管理Java微服务后端架构实战

1. 架构概述

用户账户管理系统是金融平台和电商平台的核心模块,需要支持账户创建、充值、退款、余额管理等关键功能。本篇文章将深入讲解如何基于Java微服务架构实现一个高性能、高可用、资金安全的用户账户管理系统。

1.1 系统架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
管理端 → 管理网关 → 用户服务 → 数据库

身份认证

账户创建/充值/退款处理

记录账户出入明细

返回处理结果

管理端 → 管理网关 → 用户服务

身份认证

编辑处理(站点、运营商)

返回编辑结果

1.2 核心组件

  • 管理网关(Management Gateway):负责管理用户请求的接入、身份认证、请求路由
  • 用户服务(User Service):负责用户账户管理、充值退款处理、账户明细记录
  • 数据库(MySQL):持久化账户信息、交易记录、账户明细
  • 分布式锁(Redisson):保证账户操作的并发安全
  • 事务管理:保证账户操作的原子性和一致性

2. 管理网关服务实现

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
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
/**
* 管理网关服务
* 负责管理用户请求的接入、身份认证、请求路由
*/
@RestController
@RequestMapping("/api/management/gateway")
@Slf4j
public class ManagementGatewayController {

@Autowired
private AuthService authService;

@Autowired
private UserServiceClient userServiceClient;

/**
* 管理员对用户账户充值/退款
* 流程:身份认证 → 用户充值/退款处理 → 记录账户出入明细 → 返回结果
*/
@PostMapping("/account/recharge")
public Result<AccountRechargeResult> rechargeAccount(
@RequestHeader("Authorization") String token,
@RequestBody AccountRechargeRequest request) {

try {
// 1. 身份认证
AdminInfo adminInfo = authService.authenticateAdmin(token);
if (adminInfo == null) {
return Result.error("身份认证失败");
}

// 2. 权限校验
if (!authService.hasPermission(adminInfo.getAdminId(), "ACCOUNT_RECHARGE")) {
return Result.error("无权限进行账户充值操作");
}

// 3. 调用用户服务处理充值
AccountRechargeResult result = userServiceClient.rechargeAccount(request);

// 4. 返回账户充值结果
return Result.success(result);

} catch (Exception e) {
log.error("账户充值失败: userId={}, amount={}, error={}",
request.getUserId(), request.getAmount(), e.getMessage(), e);
return Result.error("账户充值失败: " + e.getMessage());
}
}

/**
* 管理员对用户账户退款
*/
@PostMapping("/account/refund")
public Result<AccountRefundResult> refundAccount(
@RequestHeader("Authorization") String token,
@RequestBody AccountRefundRequest request) {

try {
// 1. 身份认证
AdminInfo adminInfo = authService.authenticateAdmin(token);
if (adminInfo == null) {
return Result.error("身份认证失败");
}

// 2. 权限校验
if (!authService.hasPermission(adminInfo.getAdminId(), "ACCOUNT_REFUND")) {
return Result.error("无权限进行账户退款操作");
}

// 3. 调用用户服务处理退款
AccountRefundResult result = userServiceClient.refundAccount(request);

// 4. 返回账户退款结果
return Result.success(result);

} catch (Exception e) {
log.error("账户退款失败: userId={}, amount={}, error={}",
request.getUserId(), request.getAmount(), e.getMessage(), e);
return Result.error("账户退款失败: " + e.getMessage());
}
}

/**
* 管理员对用户账户除余额之外编辑管理
* 流程:身份认证 → 编辑处理(站点、运营商) → 返回编辑结果
*/
@PostMapping("/account/edit")
public Result<AccountEditResult> editAccount(
@RequestHeader("Authorization") String token,
@RequestBody AccountEditRequest request) {

try {
// 1. 身份认证
AdminInfo adminInfo = authService.authenticateAdmin(token);
if (adminInfo == null) {
return Result.error("身份认证失败");
}

// 2. 权限校验
if (!authService.hasPermission(adminInfo.getAdminId(), "ACCOUNT_EDIT")) {
return Result.error("无权限进行账户编辑操作");
}

// 3. 调用用户服务处理编辑
AccountEditResult result = userServiceClient.editAccount(request);

// 4. 返回编辑结果
return Result.success(result);

} catch (Exception e) {
log.error("账户编辑失败: userId={}, error={}",
request.getUserId(), e.getMessage(), e);
return Result.error("账户编辑失败: " + e.getMessage());
}
}
}

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
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
/**
* 身份认证服务
*/
@Service
@Slf4j
public class AuthService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private AdminServiceClient adminServiceClient;

/**
* 管理员身份认证
* 验证Token有效性
*/
public AdminInfo authenticateAdmin(String token) {
try {
// 1. 从Token中解析管理员信息
String adminId = parseToken(token);
if (StringUtils.isEmpty(adminId)) {
return null;
}

// 2. 从缓存中获取管理员信息
String adminCacheKey = "admin:info:" + adminId;
AdminInfo adminInfo = (AdminInfo) redisTemplate.opsForValue().get(adminCacheKey);

if (adminInfo != null) {
return adminInfo;
}

// 3. 缓存未命中,调用管理员服务查询
adminInfo = adminServiceClient.getAdminInfo(adminId);

// 4. 将管理员信息写入缓存
if (adminInfo != null) {
redisTemplate.opsForValue().set(adminCacheKey, adminInfo, 30, TimeUnit.MINUTES);
}

return adminInfo;

} catch (Exception e) {
log.error("管理员身份认证失败: token={}, error={}", token, e.getMessage(), e);
return null;
}
}

/**
* 权限校验
* 检查管理员是否有指定权限
*/
public boolean hasPermission(String adminId, String permission) {
try {
// 1. 检查管理员权限缓存
String permissionKey = "admin:permission:" + adminId + ":" + permission;
Boolean hasPermission = (Boolean) redisTemplate.opsForValue().get(permissionKey);

if (hasPermission != null) {
return hasPermission;
}

// 2. 调用权限服务查询
hasPermission = adminServiceClient.checkPermission(adminId, permission);

// 3. 将权限信息写入缓存
redisTemplate.opsForValue().set(permissionKey, hasPermission, 10, TimeUnit.MINUTES);

return hasPermission;

} catch (Exception e) {
log.error("权限校验失败: adminId={}, permission={}, error={}",
adminId, permission, e.getMessage(), e);
return false;
}
}

/**
* 解析Token
*/
private String parseToken(String token) {
// JWT Token解析逻辑
if (StringUtils.isEmpty(token) || !token.startsWith("Bearer ")) {
return null;
}

String jwtToken = token.substring(7);
// 解析JWT获取adminId
return extractAdminIdFromJWT(jwtToken);
}

private String extractAdminIdFromJWT(String jwtToken) {
// JWT解析实现
// 实际实现需要使用JWT库
return "admin123";
}
}

3. 用户服务实现

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
/**
* 用户服务
* 负责用户账户管理、充值退款处理、账户明细记录
*/
@Service
@Slf4j
public class UserAccountService {

@Autowired
private UserAccountMapper userAccountMapper;

@Autowired
private AccountDetailMapper accountDetailMapper;

@Autowired
private RedissonClient redissonClient;

/**
* 用户充值处理
* 如果用户账户不存在新建账户,设定充值金额
* 如果用户账户存在,修改账户余额
*/
@Transactional(rollbackFor = Exception.class)
public AccountRechargeResult rechargeAccount(AccountRechargeRequest request) {
String lockKey = "account:lock:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 1. 查询用户账户
UserAccount account = userAccountMapper.selectByUserId(request.getUserId());

if (account == null) {
// 2. 如果用户账户不存在,新建账户
account = createUserAccount(request.getUserId());
log.info("新建用户账户: userId={}", request.getUserId());
}

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

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

// 5. 修改账户余额
BigDecimal balanceBefore = account.getBalance();
account.setBalance(account.getBalance().add(request.getAmount()));
account.setTotalIncome(account.getTotalIncome().add(request.getAmount()));
account.setUpdateTime(new Date());
userAccountMapper.updateById(account);

// 6. 记录账户出入明细
recordAccountDetail(account, request.getAmount(),
AccountDetailType.RECHARGE, balanceBefore, account.getBalance());

log.info("用户账户充值成功: userId={}, amount={}, balanceBefore={}, balanceAfter={}",
request.getUserId(), request.getAmount(), balanceBefore, account.getBalance());

// 7. 构建返回结果
AccountRechargeResult result = new AccountRechargeResult();
result.setUserId(request.getUserId());
result.setAmount(request.getAmount());
result.setBalanceBefore(balanceBefore);
result.setBalanceAfter(account.getBalance());
result.setTransactionNo(generateTransactionNo());
result.setCreateTime(new Date());

return result;

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

/**
* 用户退款处理
* 直接扣除余额
*/
@Transactional(rollbackFor = Exception.class)
public AccountRefundResult refundAccount(AccountRefundRequest request) {
String lockKey = "account:lock:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 1. 查询用户账户
UserAccount account = userAccountMapper.selectByUserId(request.getUserId());
if (account == null) {
throw new BusinessException("用户账户不存在");
}

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

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

// 4. 直接扣除余额
BigDecimal balanceBefore = account.getBalance();
account.setBalance(account.getBalance().subtract(request.getAmount()));
account.setTotalExpense(account.getTotalExpense().add(request.getAmount()));
account.setUpdateTime(new Date());
userAccountMapper.updateById(account);

// 5. 记录账户出入明细
recordAccountDetail(account, request.getAmount(),
AccountDetailType.REFUND, balanceBefore, account.getBalance());

log.info("用户账户退款成功: userId={}, amount={}, balanceBefore={}, balanceAfter={}",
request.getUserId(), request.getAmount(), balanceBefore, account.getBalance());

// 6. 构建返回结果
AccountRefundResult result = new AccountRefundResult();
result.setUserId(request.getUserId());
result.setAmount(request.getAmount());
result.setBalanceBefore(balanceBefore);
result.setBalanceAfter(account.getBalance());
result.setTransactionNo(generateTransactionNo());
result.setCreateTime(new Date());

return result;

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

/**
* 编辑处理(站点、运营商)
* 管理员对用户账户除余额之外编辑管理
*/
@Transactional(rollbackFor = Exception.class)
public AccountEditResult editAccount(AccountEditRequest request) {
String lockKey = "account:lock:" + request.getUserId();
RLock lock = redissonClient.getLock(lockKey);

try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 1. 查询用户账户
UserAccount account = userAccountMapper.selectByUserId(request.getUserId());
if (account == null) {
throw new BusinessException("用户账户不存在");
}

// 2. 编辑处理(站点、运营商)
boolean updated = false;

// 更新站点信息
if (StringUtils.isNotEmpty(request.getSite())) {
account.setSite(request.getSite());
updated = true;
}

// 更新运营商信息
if (StringUtils.isNotEmpty(request.getOperator())) {
account.setOperator(request.getOperator());
updated = true;
}

// 更新其他信息(除余额外)
if (StringUtils.isNotEmpty(request.getAccountName())) {
account.setAccountName(request.getAccountName());
updated = true;
}

if (StringUtils.isNotEmpty(request.getRemark())) {
account.setRemark(request.getRemark());
updated = true;
}

// 3. 保存更新
if (updated) {
account.setUpdateTime(new Date());
userAccountMapper.updateById(account);

log.info("用户账户编辑成功: userId={}, site={}, operator={}",
request.getUserId(), request.getSite(), request.getOperator());
}

// 4. 构建返回结果
AccountEditResult result = new AccountEditResult();
result.setUserId(request.getUserId());
result.setSuccess(true);
result.setMessage("账户编辑成功");
result.setUpdateTime(new Date());

return result;

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

/**
* 创建用户账户
*/
private UserAccount createUserAccount(Long userId) {
UserAccount account = new UserAccount();
account.setUserId(userId);
account.setAccountNo(generateAccountNo());
account.setAccountName("用户账户");
account.setBalance(BigDecimal.ZERO);
account.setFrozenAmount(BigDecimal.ZERO);
account.setAvailableAmount(BigDecimal.ZERO);
account.setTotalIncome(BigDecimal.ZERO);
account.setTotalExpense(BigDecimal.ZERO);
account.setStatus(AccountStatus.NORMAL);
account.setCurrency("CNY");
account.setCreateTime(new Date());
account.setUpdateTime(new Date());

userAccountMapper.insert(account);

return account;
}

/**
* 记录账户出入明细
*/
private void recordAccountDetail(UserAccount account, BigDecimal amount,
AccountDetailType type,
BigDecimal balanceBefore,
BigDecimal balanceAfter) {
AccountDetail detail = new AccountDetail();
detail.setAccountId(account.getId());
detail.setUserId(account.getUserId());
detail.setAccountNo(account.getAccountNo());
detail.setTransactionNo(generateTransactionNo());
detail.setType(type.getCode());
detail.setTypeName(type.getName());
detail.setAmount(amount);
detail.setBalanceBefore(balanceBefore);
detail.setBalanceAfter(balanceAfter);
detail.setDirection(type.getDirection());
detail.setRemark(type.getRemark());
detail.setCreateTime(new Date());

accountDetailMapper.insert(detail);
}

/**
* 检查充值限额
*/
private void checkRechargeLimit(UserAccount account, BigDecimal amount) {
// 检查单笔充值限额
if (account.getSingleRechargeLimit() != null &&
amount.compareTo(account.getSingleRechargeLimit()) > 0) {
throw new BusinessException("单笔充值金额超过限额");
}

// 检查日充值限额
BigDecimal dailyRechargeAmount = getDailyRechargeAmount(account.getUserId());
if (account.getDailyRechargeLimit() != null &&
dailyRechargeAmount.add(amount).compareTo(account.getDailyRechargeLimit()) > 0) {
throw new BusinessException("日充值金额超过限额");
}
}

/**
* 获取日充值金额
*/
private BigDecimal getDailyRechargeAmount(Long userId) {
Date today = new Date();
Date startOfDay = getStartOfDay(today);
Date endOfDay = getEndOfDay(today);

List<AccountDetail> details = accountDetailMapper.selectByUserIdAndTimeRange(
userId, startOfDay, endOfDay, AccountDetailType.RECHARGE.getCode());

return details.stream()
.map(AccountDetail::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}

/**
* 生成账户号
*/
private String generateAccountNo() {
return "ACC" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
}

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

/**
* 获取一天的开始时间
*/
private Date getStartOfDay(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}

/**
* 获取一天的结束时间
*/
private Date getEndOfDay(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
}

4. 数据模型定义

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
/**
* 用户账户
*/
@Data
@TableName("user_account")
public class UserAccount {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;

/**
* 用户ID
*/
private Long userId;

/**
* 账户号
*/
private String accountNo;

/**
* 账户名称
*/
private String accountName;

/**
* 账户余额
*/
private BigDecimal balance;

/**
* 冻结金额
*/
private BigDecimal frozenAmount;

/**
* 可用余额
*/
private BigDecimal availableAmount;

/**
* 累计收入
*/
private BigDecimal totalIncome;

/**
* 累计支出
*/
private BigDecimal totalExpense;

/**
* 账户状态:NORMAL-正常, FROZEN-冻结, CLOSED-关闭
*/
private String status;

/**
* 币种
*/
private String currency;

/**
* 站点
*/
private String site;

/**
* 运营商
*/
private String operator;

/**
* 单笔充值限额
*/
private BigDecimal singleRechargeLimit;

/**
* 日充值限额
*/
private BigDecimal dailyRechargeLimit;

/**
* 备注
*/
private String remark;

/**
* 创建时间
*/
private Date createTime;

/**
* 更新时间
*/
private Date updateTime;
}

4.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
/**
* 账户明细
*/
@Data
@TableName("account_detail")
public class AccountDetail {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;

/**
* 账户ID
*/
private Long accountId;

/**
* 用户ID
*/
private Long userId;

/**
* 账户号
*/
private String accountNo;

/**
* 交易号
*/
private String transactionNo;

/**
* 明细类型:RECHARGE-充值, REFUND-退款, CONSUME-消费等
*/
private String type;

/**
* 明细类型名称
*/
private String typeName;

/**
* 金额
*/
private BigDecimal amount;

/**
* 交易前余额
*/
private BigDecimal balanceBefore;

/**
* 交易后余额
*/
private BigDecimal balanceAfter;

/**
* 方向:INCOME-收入, EXPENSE-支出
*/
private String direction;

/**
* 备注
*/
private String remark;

/**
* 创建时间
*/
private Date createTime;
}

4.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
/**
* 账户明细类型枚举
*/
public enum AccountDetailType {
RECHARGE("RECHARGE", "充值", "INCOME", "账户充值"),
REFUND("REFUND", "退款", "EXPENSE", "账户退款"),
CONSUME("CONSUME", "消费", "EXPENSE", "账户消费"),
FREEZE("FREEZE", "冻结", "EXPENSE", "账户冻结"),
UNFREEZE("UNFREEZE", "解冻", "INCOME", "账户解冻");

private final String code;
private final String name;
private final String direction;
private final String remark;

AccountDetailType(String code, String name, String direction, String remark) {
this.code = code;
this.name = name;
this.direction = direction;
this.remark = remark;
}

public String getCode() {
return code;
}

public String getName() {
return name;
}

public String getDirection() {
return direction;
}

public String getRemark() {
return remark;
}
}

4.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
/**
* 账户状态枚举
*/
public enum AccountStatus {
NORMAL("NORMAL", "正常"),
FROZEN("FROZEN", "冻结"),
CLOSED("CLOSED", "关闭");

private final String code;
private final String name;

AccountStatus(String code, String name) {
this.code = code;
this.name = name;
}

public String getCode() {
return code;
}

public String getName() {
return name;
}
}

5. 请求和响应模型

5.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
/**
* 账户充值请求
*/
@Data
public class AccountRechargeRequest {
/**
* 用户ID
*/
private Long userId;

/**
* 充值金额
*/
private BigDecimal amount;

/**
* 充值方式
*/
private String rechargeType;

/**
* 备注
*/
private String remark;
}

5.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
/**
* 账户退款请求
*/
@Data
public class AccountRefundRequest {
/**
* 用户ID
*/
private Long userId;

/**
* 退款金额
*/
private BigDecimal amount;

/**
* 退款原因
*/
private String reason;

/**
* 备注
*/
private String remark;
}

5.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
/**
* 账户编辑请求
*/
@Data
public class AccountEditRequest {
/**
* 用户ID
*/
private Long userId;

/**
* 站点
*/
private String site;

/**
* 运营商
*/
private String operator;

/**
* 账户名称
*/
private String accountName;

/**
* 备注
*/
private String remark;
}

6. 数据库Mapper实现

6.1 UserAccountMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 用户账户Mapper
*/
@Mapper
public interface UserAccountMapper extends BaseMapper<UserAccount> {

/**
* 根据用户ID查询账户
*/
@Select("SELECT * FROM user_account WHERE user_id = #{userId} AND deleted = 0")
UserAccount selectByUserId(@Param("userId") Long userId);

/**
* 根据账户号查询账户
*/
@Select("SELECT * FROM user_account WHERE account_no = #{accountNo} AND deleted = 0")
UserAccount selectByAccountNo(@Param("accountNo") String accountNo);
}

6.2 AccountDetailMapper

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
/**
* 账户明细Mapper
*/
@Mapper
public interface AccountDetailMapper extends BaseMapper<AccountDetail> {

/**
* 根据用户ID和时间范围查询明细
*/
@Select("SELECT * FROM account_detail " +
"WHERE user_id = #{userId} " +
"AND type = #{type} " +
"AND create_time >= #{startTime} " +
"AND create_time <= #{endTime} " +
"ORDER BY create_time DESC")
List<AccountDetail> selectByUserIdAndTimeRange(
@Param("userId") Long userId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("type") String type);

/**
* 查询用户账户明细列表
*/
@Select("<script>" +
"SELECT * FROM account_detail " +
"WHERE user_id = #{userId} " +
"<if test='type != null and type != \"\"'>" +
"AND type = #{type} " +
"</if>" +
"<if test='startTime != null'>" +
"AND create_time >= #{startTime} " +
"</if>" +
"<if test='endTime != null'>" +
"AND create_time <= #{endTime} " +
"</if>" +
"ORDER BY create_time DESC " +
"</script>")
List<AccountDetail> selectAccountDetailList(
@Param("userId") Long userId,
@Param("type") String type,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime);
}

7. 数据库表设计

7.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
-- 用户账户表
CREATE TABLE `user_account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`account_name` varchar(100) DEFAULT 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 '累计支出',
`status` varchar(20) NOT NULL DEFAULT 'NORMAL' COMMENT '账户状态:NORMAL-正常, FROZEN-冻结, CLOSED-关闭',
`currency` varchar(10) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`site` varchar(100) DEFAULT NULL COMMENT '站点',
`operator` varchar(100) DEFAULT NULL COMMENT '运营商',
`single_recharge_limit` decimal(15,2) DEFAULT NULL COMMENT '单笔充值限额',
`daily_recharge_limit` decimal(15,2) DEFAULT NULL COMMENT '日充值限额',
`remark` 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_user_id` (`user_id`),
UNIQUE KEY `uk_account_no` (`account_no`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户账户表';

7.2 账户明细表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 账户明细表
CREATE TABLE `account_detail` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`account_id` bigint(20) NOT NULL COMMENT '账户ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`account_no` varchar(32) NOT NULL COMMENT '账户号',
`transaction_no` varchar(64) NOT NULL COMMENT '交易号',
`type` varchar(20) NOT NULL COMMENT '明细类型:RECHARGE-充值, REFUND-退款, CONSUME-消费等',
`type_name` varchar(50) 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 '交易后余额',
`direction` varchar(20) NOT NULL COMMENT '方向:INCOME-收入, EXPENSE-支出',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_transaction_no` (`transaction_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_account_id` (`account_id`),
KEY `idx_type` (`type`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户明细表';

8. 配置类

8.1 Redisson配置

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
/**
* Redisson配置
*/
@Configuration
public class RedissonConfig {

/**
* 配置并创建RedissonClient Bean实例
* Redisson是一个分布式Java对象和服务,基于Redis实现
*
* @return RedissonClient Redisson客户端实例,用于与Redis服务器交互
*/
@Bean
public RedissonClient redissonClient() {
// 创建Redisson配置对象
Config config = new Config();
// 配置单服务器模式
config.useSingleServer()
// 设置Redis服务器地址
.setAddress("redis://localhost:6379")
// 设置数据库索引(0-15)
.setDatabase(0)
// 设置连接池大小
.setConnectionPoolSize(10)
// 设置最小空闲连接数
.setConnectionMinimumIdleSize(5);
// 根据配置创建Redisson客户端实例并返回
return Redisson.create(config);
}
}

8.2 事务配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 事务配置
*/
/**
* 事务配置类
* 使用@Configuration注解标记这是一个配置类
* 使用@EnableTransactionManagement注解启用Spring的事务管理功能
*/
@Configuration
@EnableTransactionManagement
public class TransactionConfig {

/**
* 定义事务管理器Bean
* @param dataSource 数据源,用于事务管理
* @return 返回一个基于数据源的事务管理器实例
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

9. 性能优化策略

9.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
/**
* 账户余额缓存服务
*/
@Service
@Slf4j
public class AccountCacheService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private UserAccountMapper userAccountMapper;

/**
* 获取账户余额(带缓存)
*/
public BigDecimal getAccountBalance(Long userId) {
String cacheKey = "account:balance:" + userId;
BigDecimal balance = (BigDecimal) redisTemplate.opsForValue().get(cacheKey);

if (balance != null) {
return balance;
}

// 缓存未命中,查询数据库
UserAccount account = userAccountMapper.selectByUserId(userId);
if (account != null) {
balance = account.getBalance();
// 写入缓存,过期时间5分钟
redisTemplate.opsForValue().set(cacheKey, balance, 5, TimeUnit.MINUTES);
}

return balance != null ? balance : BigDecimal.ZERO;
}

/**
* 更新账户余额缓存
*/
public void updateAccountBalanceCache(Long userId, BigDecimal balance) {
String cacheKey = "account:balance:" + userId;
redisTemplate.opsForValue().set(cacheKey, balance, 5, TimeUnit.MINUTES);
}

/**
* 删除账户余额缓存
*/
public void deleteAccountBalanceCache(Long userId) {
String cacheKey = "account:balance:" + userId;
redisTemplate.delete(cacheKey);
}
}

9.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
/**
* 批量查询优化
*/
@Service
@Slf4j
public class BatchAccountService {

@Autowired
private UserAccountMapper userAccountMapper;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 批量查询账户余额
*/
public Map<Long, BigDecimal> batchGetAccountBalance(List<Long> userIds) {
Map<Long, BigDecimal> result = new HashMap<>();
List<Long> uncachedUserIds = new ArrayList<>();

// 1. 先从缓存获取
for (Long userId : userIds) {
String cacheKey = "account:balance:" + userId;
BigDecimal balance = (BigDecimal) redisTemplate.opsForValue().get(cacheKey);
if (balance != null) {
result.put(userId, balance);
} else {
uncachedUserIds.add(userId);
}
}

// 2. 批量查询数据库
if (!uncachedUserIds.isEmpty()) {
List<UserAccount> accounts = userAccountMapper.selectBatchByUserIds(uncachedUserIds);
for (UserAccount account : accounts) {
result.put(account.getUserId(), account.getBalance());
// 写入缓存
String cacheKey = "account:balance:" + account.getUserId();
redisTemplate.opsForValue().set(cacheKey, account.getBalance(), 5, TimeUnit.MINUTES);
}
}

return result;
}
}

10. 总结

本文详细介绍了用户账户创建、充值、退款、管理的Java微服务架构实现,包括:

  1. 管理网关服务:负责管理用户请求接入、身份认证、请求路由
  2. 用户服务:负责用户账户管理、充值退款处理、账户明细记录
  3. 账户创建:如果用户账户不存在,自动创建账户
  4. 账户充值:支持账户创建和余额修改
  5. 账户退款:直接扣除余额
  6. 账户编辑:支持除余额之外的账户信息编辑(站点、运营商等)
  7. 账户明细记录:完整记录所有账户出入明细
  8. 并发安全:使用分布式锁保证账户操作的并发安全
  9. 事务管理:保证账户操作的原子性和一致性

该架构具有以下优势:

  • 资金安全:分布式锁 + 事务管理,保证资金安全
  • 高性能:账户余额缓存,查询延迟<5ms
  • 高可用:数据库持久化,支持数据恢复
  • 可扩展:微服务架构,支持水平扩展
  • 完整性:完整的账户明细记录,支持对账和审计

通过本文的实战代码,可以快速搭建一个高性能、高可用的用户账户管理系统。