|
@@ -21,6 +21,7 @@ import com.fs.common.service.ISmsService;
|
|
|
import com.fs.common.utils.ServletUtils;
|
|
import com.fs.common.utils.ServletUtils;
|
|
|
import com.fs.common.utils.ip.IpUtils;
|
|
import com.fs.common.utils.ip.IpUtils;
|
|
|
import com.fs.common.utils.sign.Md5Utils;
|
|
import com.fs.common.utils.sign.Md5Utils;
|
|
|
|
|
+import com.fs.his.utils.PhoneUtil;
|
|
|
import com.fs.core.config.WxOpenProperties;
|
|
import com.fs.core.config.WxOpenProperties;
|
|
|
import com.fs.course.domain.LuckyBag;
|
|
import com.fs.course.domain.LuckyBag;
|
|
|
import com.fs.course.domain.LuckyBagCollectRecord;
|
|
import com.fs.course.domain.LuckyBagCollectRecord;
|
|
@@ -109,11 +110,20 @@ public class AppLoginController extends AppBaseController{
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private OpenIMService openIMService;
|
|
private OpenIMService openIMService;
|
|
|
|
|
|
|
|
|
|
+ /** 同一手机号每天最多发送次数 */
|
|
|
|
|
+ private static final int SMS_PHONE_DAILY_LIMIT = 2;
|
|
|
|
|
+ /** 同一IP每天最多对多少个不同手机号发注册验证码 */
|
|
|
|
|
+ private static final int SMS_IP_DISTINCT_PHONE_LIMIT = 3;
|
|
|
|
|
+ /** 注册验证码全站每日总上限(按日常真实注册量收紧,防止换号烧钱) */
|
|
|
|
|
+ private static final int SMS_REGISTER_GLOBAL_DAILY_LIMIT = 800;
|
|
|
|
|
+ /** 短信验证码有效期(秒),有效期内不允许重发 */
|
|
|
|
|
+ private static final int SMS_CODE_EXPIRE_SECONDS = 180;
|
|
|
|
|
+
|
|
|
@ApiOperation("注册app用户")
|
|
@ApiOperation("注册app用户")
|
|
|
@PostMapping("/register")
|
|
@PostMapping("/register")
|
|
|
@RepeatSubmit
|
|
@RepeatSubmit
|
|
|
public R registerDoctor(@Validated @RequestBody FsUserRegisterParam param){
|
|
public R registerDoctor(@Validated @RequestBody FsUserRegisterParam param){
|
|
|
- FsUser fsUser = findUserByPhone(param.getPhone());
|
|
|
|
|
|
|
+ /* FsUser fsUser = findUserByPhone(param.getPhone());
|
|
|
|
|
|
|
|
// if (fsUser == null) {
|
|
// if (fsUser == null) {
|
|
|
// // 尝试使用加密后的手机号查询
|
|
// // 尝试使用加密后的手机号查询
|
|
@@ -151,7 +161,7 @@ public class AppLoginController extends AppBaseController{
|
|
|
return R.error("注册失败");
|
|
return R.error("注册失败");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+*/ return R.ok("当前不允许手机号密码注册!");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@ApiOperation("登录")
|
|
@ApiOperation("登录")
|
|
@@ -170,9 +180,19 @@ public class AppLoginController extends AppBaseController{
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 注册发送短信验证码(纯后端防轰炸,前端无改动)
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 验证码有效期内不允许重发;输错验证码也不能立刻重发,须等过期后再获取。
|
|
|
|
|
+ */
|
|
|
@PostMapping("/registerSendCode")
|
|
@PostMapping("/registerSendCode")
|
|
|
public R registerSendCode(@RequestBody Map<String, String> body){
|
|
public R registerSendCode(@RequestBody Map<String, String> body){
|
|
|
String phone = body.get("phone");
|
|
String phone = body.get("phone");
|
|
|
|
|
+ String phoneCheck = validateSmsPhone(phone);
|
|
|
|
|
+ if (phoneCheck != null) {
|
|
|
|
|
+ return R.error(phoneCheck);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String encryptPhone = encryptPhone(phone);
|
|
String encryptPhone = encryptPhone(phone);
|
|
|
List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
|
|
List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
|
|
|
if(CollectionUtil.isEmpty(user)){
|
|
if(CollectionUtil.isEmpty(user)){
|
|
@@ -182,28 +202,85 @@ public class AppLoginController extends AppBaseController{
|
|
|
return R.error("此电话号码已注册");
|
|
return R.error("此电话号码已注册");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 验证码 key(存验证码,3分钟有效)
|
|
|
|
|
- String smsCodeKey = "sms:code:" + phone;
|
|
|
|
|
- // 发送冷却 key(限制60秒内不能再次发送)
|
|
|
|
|
- String smsCooldownKey = "sms:cooldown:" + phone;
|
|
|
|
|
|
|
+ // 验证码仍在有效期内:不允许重发(防刷,也避免输错后立刻再发)
|
|
|
|
|
+ if (StringUtils.isNotEmpty(redisCache.getCacheObject("sms:code:" + phone))) {
|
|
|
|
|
+ return R.error("验证码仍在有效期内,请查收短信;如未收到或输入错误,请等待验证码过期后再重新获取");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 判断是否在60秒冷却时间内
|
|
|
|
|
- if (redisCache.getCacheObject(smsCooldownKey) != null) {
|
|
|
|
|
- return R.error("验证码已发送,请稍后再试");
|
|
|
|
|
|
|
+ String limitMsg = checkRegisterSmsLimit(phone);
|
|
|
|
|
+ if (limitMsg != null) {
|
|
|
|
|
+ return R.error(limitMsg);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 生成新的验证码
|
|
|
|
|
String smsCode = VerifyCodeUtil.generateCode();
|
|
String smsCode = VerifyCodeUtil.generateCode();
|
|
|
-
|
|
|
|
|
- // 发送短信
|
|
|
|
|
smsService.sendCaptcha(phone, smsCode, "验证码");
|
|
smsService.sendCaptcha(phone, smsCode, "验证码");
|
|
|
|
|
+ redisCache.setCacheObject("sms:code:" + phone, smsCode, SMS_CODE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
|
|
+ return R.ok("验证码已发送");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 缓存验证码(3分钟有效)
|
|
|
|
|
- redisCache.setCacheObject(smsCodeKey, smsCode, 180, TimeUnit.SECONDS);
|
|
|
|
|
- // 设置冷却时间(60秒内不能再发)
|
|
|
|
|
- redisCache.setCacheObject(smsCooldownKey, "1", 60, TimeUnit.SECONDS);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验手机号:格式 + 正规号段白名单(虚拟号/物联网号直接拒绝)
|
|
|
|
|
+ */
|
|
|
|
|
+ private String validateSmsPhone(String phone) {
|
|
|
|
|
+ if (StringUtils.isBlank(phone) || !phone.matches("^1[3-9]\\d{9}$")) {
|
|
|
|
|
+ return "手机号格式不正确";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!PhoneUtil.isValidSmsPhone(phone)) {
|
|
|
|
|
+ logger.warn("拦截非白名单号段短信请求, phone={}, ip={}", phone,
|
|
|
|
|
+ IpUtils.getIpAddr(ServletUtils.getRequest()));
|
|
|
|
|
+ return "该手机号暂不支持接收验证码,请更换手机号";
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return R.ok("验证码已发送");
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 注册短信发送限制:冷却 + 单号日限 + IP换号限制 + 全站日预算
|
|
|
|
|
+ */
|
|
|
|
|
+ private String checkRegisterSmsLimit(String phone) {
|
|
|
|
|
+ String cooldownKey = "sms:cooldown:" + phone;
|
|
|
|
|
+ if (!redisCache.setIfAbsent(cooldownKey, "1", 60, TimeUnit.SECONDS)) {
|
|
|
|
|
+ return "验证码已发送,请稍后再试";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String phoneDailyKey = "sms:daily:phone:" + phone;
|
|
|
|
|
+ Long phoneDailyCount = redisCache.incr(phoneDailyKey, 1L);
|
|
|
|
|
+ if (phoneDailyCount != null && phoneDailyCount == 1L) {
|
|
|
|
|
+ redisCache.expire(phoneDailyKey, 1, TimeUnit.DAYS);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (phoneDailyCount != null && phoneDailyCount > SMS_PHONE_DAILY_LIMIT) {
|
|
|
|
|
+ redisCache.deleteObject(cooldownKey);
|
|
|
|
|
+ return "今日发送次数已达上限,请明天再试";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
|
|
|
|
|
+ if (StringUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
|
|
|
|
+ String ipPhonesKey = "sms:ip:phones:" + ip;
|
|
|
|
|
+ redisCache.redisTemplate.opsForSet().add(ipPhonesKey, phone);
|
|
|
|
|
+ Long distinctPhones = redisCache.redisTemplate.opsForSet().size(ipPhonesKey);
|
|
|
|
|
+ if (distinctPhones != null && distinctPhones == 1L) {
|
|
|
|
|
+ redisCache.expire(ipPhonesKey, 1, TimeUnit.DAYS);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (distinctPhones != null && distinctPhones > SMS_IP_DISTINCT_PHONE_LIMIT) {
|
|
|
|
|
+ redisCache.redisTemplate.opsForSet().remove(ipPhonesKey, phone);
|
|
|
|
|
+ redisCache.decr(phoneDailyKey, 1L);
|
|
|
|
|
+ redisCache.deleteObject(cooldownKey);
|
|
|
|
|
+ logger.warn("拦截同IP换号轰炸, ip={}, phone={}, distinct={}", ip, phone, distinctPhones);
|
|
|
|
|
+ return "操作过于频繁,请稍后再试";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String globalKey = "sms:register:global:daily";
|
|
|
|
|
+ Long globalCount = redisCache.incr(globalKey, 1L);
|
|
|
|
|
+ if (globalCount != null && globalCount == 1L) {
|
|
|
|
|
+ redisCache.expire(globalKey, 1, TimeUnit.DAYS);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (globalCount != null && globalCount > SMS_REGISTER_GLOBAL_DAILY_LIMIT) {
|
|
|
|
|
+ redisCache.decr(phoneDailyKey, 1L);
|
|
|
|
|
+ redisCache.deleteObject(cooldownKey);
|
|
|
|
|
+ logger.error("注册短信全站日限额已用尽, count={}", globalCount);
|
|
|
|
|
+ return "系统繁忙,请稍后再试";
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -223,10 +300,10 @@ public class AppLoginController extends AppBaseController{
|
|
|
}
|
|
}
|
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
|
- return R.error("验证码已过期,请重新发送");
|
|
|
|
|
|
|
+ return R.error("验证码已过期,请重新获取");
|
|
|
}
|
|
}
|
|
|
if (!redisCode.equals(code)) {
|
|
if (!redisCode.equals(code)) {
|
|
|
- return R.error("验证码错误");
|
|
|
|
|
|
|
+ return R.error("验证码错误,请输入正确验证码;如需重新获取,请等待当前验证码过期后再试");
|
|
|
}
|
|
}
|
|
|
FsUser user = new FsUser();
|
|
FsUser user = new FsUser();
|
|
|
// 创建新用户
|
|
// 创建新用户
|
|
@@ -240,6 +317,7 @@ public class AppLoginController extends AppBaseController{
|
|
|
user.setCreateTime(new Date());
|
|
user.setCreateTime(new Date());
|
|
|
user.setAppCreateTime(new Date());
|
|
user.setAppCreateTime(new Date());
|
|
|
if (userService.insertFsUser(user) > 0) {
|
|
if (userService.insertFsUser(user) > 0) {
|
|
|
|
|
+ redisCache.deleteObject("sms:code:" + phone);
|
|
|
return R.ok("注册成功");
|
|
return R.ok("注册成功");
|
|
|
} else {
|
|
} else {
|
|
|
return R.error("注册失败");
|
|
return R.error("注册失败");
|
|
@@ -578,10 +656,10 @@ public class AppLoginController extends AppBaseController{
|
|
|
}
|
|
}
|
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
|
- return R.error("验证码已过期,请重新发送");
|
|
|
|
|
|
|
+ return R.error("验证码已过期,请重新获取");
|
|
|
}
|
|
}
|
|
|
if (!redisCode.equals(code)) {
|
|
if (!redisCode.equals(code)) {
|
|
|
- return R.error("验证码错误");
|
|
|
|
|
|
|
+ return R.error("验证码错误,请输入正确验证码;如需重新获取,请等待当前验证码过期后再试");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
FsUser info=user.get(0);
|
|
FsUser info=user.get(0);
|
|
@@ -645,10 +723,10 @@ public class AppLoginController extends AppBaseController{
|
|
|
}
|
|
}
|
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
String redisCode = redisCache.getCacheObject("sms:code:" + phone);
|
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
if (StringUtils.isEmpty(redisCode)){
|
|
|
- return R.error("验证码已过期,请重新发送");
|
|
|
|
|
|
|
+ return R.error("验证码已过期,请重新获取");
|
|
|
}
|
|
}
|
|
|
if (!redisCode.equals(code)) {
|
|
if (!redisCode.equals(code)) {
|
|
|
- return R.error("验证码错误");
|
|
|
|
|
|
|
+ return R.error("验证码错误,请输入正确验证码;如需重新获取,请等待当前验证码过期后再试");
|
|
|
}
|
|
}
|
|
|
String password = Md5Utils.hash(newPassword);
|
|
String password = Md5Utils.hash(newPassword);
|
|
|
return userService.updatePasswordByPhone(password,encryptPhone);
|
|
return userService.updatePasswordByPhone(password,encryptPhone);
|
|
@@ -1042,6 +1120,11 @@ public class AppLoginController extends AppBaseController{
|
|
|
@PostMapping("/sendCode")
|
|
@PostMapping("/sendCode")
|
|
|
public R sendCode(@RequestBody Map<String, String> body){
|
|
public R sendCode(@RequestBody Map<String, String> body){
|
|
|
String phone = body.get("phone");
|
|
String phone = body.get("phone");
|
|
|
|
|
+ String phoneCheck = validateSmsPhone(phone);
|
|
|
|
|
+ if (phoneCheck != null) {
|
|
|
|
|
+ return R.error(phoneCheck);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String encryptPhone = encryptPhone(phone);
|
|
String encryptPhone = encryptPhone(phone);
|
|
|
List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
|
|
List<FsUser> user = userService.selectFsUserListByPhone(encryptPhone);
|
|
|
if(CollectionUtil.isEmpty(user)){
|
|
if(CollectionUtil.isEmpty(user)){
|
|
@@ -1051,27 +1134,29 @@ public class AppLoginController extends AppBaseController{
|
|
|
return R.error("此电话号码未绑定用户");
|
|
return R.error("此电话号码未绑定用户");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 验证码 key(3分钟有效)
|
|
|
|
|
- String smsCodeKey = "sms:code:" + phone;
|
|
|
|
|
- // 冷却 key(60秒内不能重复发送)
|
|
|
|
|
- String smsCooldownKey = "sms:cooldown:" + phone;
|
|
|
|
|
|
|
+ // 验证码仍在有效期内:不允许重发
|
|
|
|
|
+ if (StringUtils.isNotEmpty(redisCache.getCacheObject("sms:code:" + phone))) {
|
|
|
|
|
+ return R.error("验证码仍在有效期内,请查收短信;如未收到或输入错误,请等待验证码过期后再重新获取");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 判断是否在 60 秒冷却期
|
|
|
|
|
- if (redisCache.getCacheObject(smsCooldownKey) != null) {
|
|
|
|
|
|
|
+ String smsCooldownKey = "sms:cooldown:" + phone;
|
|
|
|
|
+ if (!redisCache.setIfAbsent(smsCooldownKey, "1", 60, TimeUnit.SECONDS)) {
|
|
|
return R.error("验证码已发送,请稍后再试");
|
|
return R.error("验证码已发送,请稍后再试");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 生成新验证码
|
|
|
|
|
- String smsCode = VerifyCodeUtil.generateCode();
|
|
|
|
|
|
|
+ String phoneDailyKey = "sms:daily:phone:" + phone;
|
|
|
|
|
+ Long phoneDailyCount = redisCache.incr(phoneDailyKey, 1L);
|
|
|
|
|
+ if (phoneDailyCount != null && phoneDailyCount == 1L) {
|
|
|
|
|
+ redisCache.expire(phoneDailyKey, 1, TimeUnit.DAYS);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (phoneDailyCount != null && phoneDailyCount > SMS_PHONE_DAILY_LIMIT) {
|
|
|
|
|
+ redisCache.deleteObject(smsCooldownKey);
|
|
|
|
|
+ return R.error("今日发送次数已达上限,请明天再试");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 发送短信
|
|
|
|
|
|
|
+ String smsCode = VerifyCodeUtil.generateCode();
|
|
|
smsService.sendCaptcha(phone, smsCode, "验证码");
|
|
smsService.sendCaptcha(phone, smsCode, "验证码");
|
|
|
-
|
|
|
|
|
- // 缓存验证码(3分钟有效)
|
|
|
|
|
- redisCache.setCacheObject(smsCodeKey, smsCode, 180, TimeUnit.SECONDS);
|
|
|
|
|
- // 设置冷却时间(60秒内不能再发)
|
|
|
|
|
- redisCache.setCacheObject(smsCooldownKey, "1", 60, TimeUnit.SECONDS);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ redisCache.setCacheObject("sms:code:" + phone, smsCode, SMS_CODE_EXPIRE_SECONDS, TimeUnit.SECONDS);
|
|
|
return R.ok("验证码已发送");
|
|
return R.ok("验证码已发送");
|
|
|
}
|
|
}
|
|
|
|
|
|