{{wikiTitle}}
手机号登录
目录:
一、功能概述
本模块采用「一号通」短信服务实现手机号登录验证码发送功能;
核心流程包括:验证手机号合法性 → 检查一号通服务配置 → 发送短信 → Redis缓存验证码与频率控制。
以下是关键代码模块的说明及扩展指导:
二、代码逻辑
1.控制器层(Controller)
入口方法:仅判断发送状态,具体逻辑下沉至服务层
/**
* 发送短信登录验证码
*
* @param phone 手机号码
* @return 发送是否成功
*/
@ApiOperation(value = "发送短信登录验证码")
@RequestMapping(value = "/sendCode", method = RequestMethod.POST)
@ApiImplicitParams({
@ApiImplicitParam(name = "phone", value = "手机号码", required = true)
})
public CommonResult<Object> sendCode(@RequestParam String phone) {
if (smsService.sendCommonCode(phone)) {
return CommonResult.success("发送成功");
} else {
return CommonResult.failed("发送失败");
}
}
2. 服务层实现 (ServiceImpl)
核心逻辑:
手机号格式校验:使用ValidateFormUtil.isPhone正则校验(自定义规则可修改ValidateFormUtil);
一号通服务健康检查:
1.checkAccount()验证账号登录状态;
2.info()获取短信服务开通状态及剩余条数;
发送频率控制:通过Redis键 SMS_VALIDATE_PHONE_NUM + phone实现60秒内防重复提交;
分发至发送器:调用sendSms处理验证码或业务通知短信。
/**
* 发送公共验证码
*
* @param phone 手机号
* @return Boolean
* 1.校验后台是否配置一号通
* 2.一号通是否剩余短信条数
* 3.发送短信
*/
@Override
public Boolean sendCommonCode(String phone) {
ValidateFormUtil.isPhone(phone,"手机号码错误");
Boolean checkAccount = onePassService.checkAccount();
if (!checkAccount) {
throw new CrmebException("发送短信请先登录一号通账号");
}
JSONObject info = onePassService.info();
JSONObject smsObject = info.getJSONObject("sms");
Integer open = smsObject.getInteger("open");
if (!open.equals(1)) {
throw new CrmebException("发送短信请先开通一号通账号服务");
}
if (smsObject.getInteger("num") <= 0) {
throw new CrmebException("一号通账号服务余量不足");
}
if (redisUtil.exists(SmsConstants.SMS_VALIDATE_PHONE_NUM + phone)) {
throw new CrmebException("您的短信发送过于频繁,请稍后再试");
}
return sendSms(phone, SmsConstants.SMS_CONFIG_TYPE_VERIFICATION_CODE, null);
}
3. 短信发送核心逻辑 (sendSms)
1.验证码特殊处理
可配置项:
过期时间:通过系统配置表system_config的sms_code_expire配置(单位:分钟);
验证码长度:修改CrmebUtil.randomCount范围(如4位数设为1000-9999)。
2.二开指南
新增短信类型需:
在SmsConstants中定义常量(如SMS_CONFIG_TYPE_NEW_FEATURE);
扩展switch-case分支配置模板ID;
在一号通平台申请对应模板并审核通过。
/**
* 发送短信
* 验证码特殊处理其他的参数自行根据要求处理
* 参数处理逻辑 {code:value,code1:value1}
*
* @param phone String 手机号码
* @return boolean
*/
private Boolean sendSms(String phone, Integer tag, HashMap<String, Object> pram) {
SendSmsVo sendSmsVo = new SendSmsVo();
sendSmsVo.setMobile(phone);
if (tag.equals(SmsConstants.SMS_CONFIG_TYPE_VERIFICATION_CODE)) {// 验证码 特殊处理 code
//获取短信验证码过期时间
String codeExpireStr = systemConfigService.getValueByKey(Constants.CONFIG_KEY_SMS_CODE_EXPIRE);
if (StrUtil.isBlank(codeExpireStr) || Integer.parseInt(codeExpireStr) == 0) {
codeExpireStr = Constants.NUM_FIVE + "";// 默认5分钟过期
}
Integer code = CrmebUtil.randomCount(111111, 999999);
HashMap<String, Object> justPram = new HashMap<>();
justPram.put("code", code);
justPram.put("time", codeExpireStr);
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_VERIFICATION_CODE_TEMP_ID);
sendSmsVo.setContent(JSONObject.toJSONString(justPram));
Boolean aBoolean = commonSendSms(sendSmsVo);
if (!aBoolean) {
throw new CrmebException("发送短信失败,请联系后台管理员");
}
// 将验证码存入redis
redisUtil.set(userService.getValidateCodeRedisKey(phone), code, Long.valueOf(codeExpireStr), TimeUnit.MINUTES);
redisUtil.set(SmsConstants.SMS_VALIDATE_PHONE_NUM + phone, 1, 60L);
return aBoolean;
}
// 以下部分实时性不高暂时还是使用队列发送
sendSmsVo.setContent(JSONObject.toJSONString(pram));
switch (tag) {
case SmsConstants.SMS_CONFIG_TYPE_LOWER_ORDER_SWITCH: // 支付成功短信提醒 pay_price order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_LOWER_ORDER_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_DELIVER_GOODS_SWITCH: // 发货短信提醒 nickname store_name
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_DELIVER_GOODS_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_CONFIRM_TAKE_OVER_SWITCH: // 确认收货短信提醒 order_id store_name
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_CONFIRM_TAKE_OVER_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_ADMIN_LOWER_ORDER_SWITCH: // 用户下单管理员短信提醒 admin_name order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_ADMIN_LOWER_ORDER_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_ADMIN_PAY_SUCCESS_SWITCH: // 支付成功管理员短信提醒 admin_name order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_ADMIN_PAY_SUCCESS_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_ADMIN_REFUND_SWITCH: // 用户确认收货管理员短信提醒 admin_name order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_ADMIN_REFUND_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_ADMIN_CONFIRM_TAKE_OVER_SWITCH: // 用户发起退款管理员短信提醒 admin_name order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_ADMIN_CONFIRM_TAKE_OVER_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_PRICE_REVISION_SWITCH: // 改价短信提醒 order_id pay_price
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_PRICE_REVISION_SWITCH_TEMP_ID);
break;
case SmsConstants.SMS_CONFIG_TYPE_ORDER_PAY_FALSE: // 订单未支付 order_id
sendSmsVo.setTemplate(SmsConstants.SMS_CONFIG_ORDER_PAY_FALSE_TEMP_ID);
break;
}
return commonSendSms(sendSmsVo);
}
4. HTTP请求发送器 (commonSendSms)
关键参数:
请求头:通过onePassUtil.getCommonHeader()自动注入账号Token;
模板参数:需与一号通平台模板中变量名对应(如 ${code})。
/**
* 公共发送短信
*
* @param sendSmsVo 发送短信对象
* @return 是否发送成功
*/
private Boolean commonSendSms(SendSmsVo sendSmsVo) {
try {
String result;
String token = onePassUtil.getToken();
HashMap<String, String> header = onePassUtil.getCommonHeader(token);
Map<String, Object> map = (Map<String, Object>) JSONObject.parseObject(sendSmsVo.getContent());
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("phone", sendSmsVo.getMobile());
param.add("temp_id", sendSmsVo.getTemplate());
map.forEach((key, value) -> param.add(StrUtil.format(SmsConstants.SMS_COMMON_PARAM_FORMAT, key), value));
logger.info("============发送短信=========header = " + header);
result = restTemplateUtil.postFromUrlencoded(OnePassConstants.ONE_PASS_API_URL + OnePassConstants.ONE_PASS_API_SEND_URI, param, header);
checkResult(result);
} catch (Exception e) {
e.printStackTrace();
logger.error("发送短信失败:" + e.getMessage());
return false;
}
return true;
}
三、二开策略
更换短信服务商
步骤:
1.实现新服务商的SmsService接口(重写sendCommonCode方法);
2.将@Service(“smsService”)注解切换至新实现类;
3.移除原有的一号通依赖检查逻辑。
注意事项:
1.需适配新服务商的API返回格式;
2.更新频率控制策略的Redis键命名(避免与旧服务冲突)。
3.建议还是用原有短信服务。