『博客开发日记-后台』之获取手机验证码接口的实现

本文最后更新于 2026年5月22日 下午

获取手机验证码接口的实现


获取手机验证码接口的需求

这是后台用户需要换绑手机号时进行发送验证码操作的

校验手机号不能为空

校验手机号格式

记录IP地址,防止大量请求发送验证码

获得验证码并存入redis


代码实现

在 AdminUserController 中

1
2
3
4
5
6
7
@PostMapping("/mobile/code")
@SystemLog(businessName = "获取手机验证码接口")
@ApiOperation(value = "获取手机验证码接口", notes = "用户解绑手机时获取手机验证码", response = String.class)
public ResponseResult getMobileCode(String mobile)
{
return adminUserService.getMobileCode(mobile);
}

在 ContentTypeValidationFilter 中给接口放行


在 AdminUserServiceImpl 中

由于很多辅助方法在后面获取邮箱验证码也可以用到

所以在这里也写上了

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
//发送手机验证码
@Override
public ResponseResult getMobileCode(String mobile)
{
//校验手机号不能为空
if (!StringUtils.hasText(mobile)) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "手机号不能为空");
}

mobile = mobile.trim();

//校验手机号格式
if (!mobile.matches("^1[3-9]\\d{9}$")) {
return ResponseResult.errorResult(AppHttpCodeEnum.PHONE_FORMAT_ERROR);
}

//记录IP地址
String clientIp = getClientIp();
ResponseResult limitResult = checkCodeLimit(mobile, clientIp);
if (limitResult != null) {
return limitResult;
}

//获得验证码并存入redis
String code = generateCode();
String redisKey = saveCodeToRedis(mobile, code, clientIp);

try {
aliyunSmsServiceImpl.sendVerificationCode(mobile, code);
} catch (Exception e) {
redisCache.deleteObject(redisKey);
redisCache.deleteObject(String.format("admin:send:time:%s", mobile));
log.error("发送手机验证码失败, mobile={}, error={}", mobile, e.getMessage(), e);
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "验证码发送失败,请稍后重试");
}

return ResponseResult.okResult();
}

private String generateCode()
{
Random random = new Random();
return String.valueOf(random.nextInt(900000) + 100000);
}

//获取ip地址
private String getClientIp()
{
String clientIp = "unknown";
try {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
clientIp = IpUtils.getIpAddr(request);
}
} catch (Exception e) {
log.warn("获取客户端IP失败: {}", e.getMessage());
}
return clientIp;
}

//对验证码发送次数进行限制
private ResponseResult checkCodeLimit(String target, String clientIp)
{
//检查IP日发送次数限制
String dayKey = String.format("admin:ip:limit:day:%s", clientIp);
Integer dayCount = redisCache.getCacheObject(dayKey);
if (dayCount != null && dayCount >= 50) {
return ResponseResult.errorResult(AppHttpCodeEnum.IP_REQUEST_LIMIT, "系统检测到异常行为,请24小时后再试或联系管理员");
}

//检查同账号发送频率限制
String sendTimeKey = String.format("admin:send:time:%s", target);
String lastSendTime = redisCache.getCacheObject(sendTimeKey);
if (StringUtils.hasText(lastSendTime)) {
return ResponseResult.errorResult(AppHttpCodeEnum.SMS_SEND_FREQUENTLY, "验证码发送过于频繁,请1分钟后再试");
}

//检查IP发送频率限制
String minuteKey = String.format("admin:ip:limit:minute:%s", clientIp);
Integer minuteCount = redisCache.getCacheObject(minuteKey);
if (minuteCount != null && minuteCount >= 5) {
return ResponseResult.errorResult(AppHttpCodeEnum.IP_REQUEST_LIMIT, "您的操作过于频繁,请1分钟后再试");
}

//检测每小时发送频率
String hourKey = String.format("admin:ip:limit:hour:%s", clientIp);
Integer hourCount = redisCache.getCacheObject(hourKey);
if (hourCount != null && hourCount >= 20) {
return ResponseResult.errorResult(AppHttpCodeEnum.IP_REQUEST_LIMIT, "您的操作过于频繁,请1小时后再试");
}

return null;
}

//保存记录到redis
private String saveCodeToRedis(String target, String code, String clientIp)
{
String redisKey = String.format("admin:code:%s", target);
String sendTimeKey = String.format("admin:send:time:%s", target);
String minuteKey = String.format("admin:ip:limit:minute:%s", clientIp);
String hourKey = String.format("admin:ip:limit:hour:%s", clientIp);
String dayKey = String.format("admin:ip:limit:day:%s", clientIp);

Integer minuteCount = redisCache.getCacheObject(minuteKey);
Integer hourCount = redisCache.getCacheObject(hourKey);
Integer dayCount = redisCache.getCacheObject(dayKey);

redisCache.setCacheObject(redisKey, code, 5, TimeUnit.MINUTES);
redisCache.setCacheObject(sendTimeKey, String.valueOf(System.currentTimeMillis()), 1, TimeUnit.MINUTES);
redisCache.setCacheObject(minuteKey, minuteCount == null ? 1 : minuteCount + 1, 1, TimeUnit.MINUTES);
redisCache.setCacheObject(hourKey, hourCount == null ? 1 : hourCount + 1, 1, TimeUnit.HOURS);
redisCache.setCacheObject(dayKey, dayCount == null ? 1 : dayCount + 1, 1, TimeUnit.DAYS);

return redisKey;
}





PS:该系列只做为作者学习开发项目做的笔记用

不一定符合读者来学习,仅供参考


预告

后续会记录博客的开发过程

每次学习会做一份笔记来进行发表

“一花一世界,一叶一菩提”


版权所有 © 2026 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『博客开发日记-后台』之获取手机验证码接口的实现
http://example.com/2026/05/22/『博客开发日记-后台』之获取手机验证码接口的实现/
作者
云梦泽
发布于
2026年5月22日
更新于
2026年5月22日
许可协议