帮助文档
{{userInfo.nickname}}
用户设置 退出登录

{{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.建议还是用原有短信服务。

{{cateWiki.like_num}}人点赞
0人点赞
评论({{cateWiki.comment_num}}) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} {{cateWiki.page_view_num}}人看过该文档
评论(0) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} 538人看过该文档
评论
{{item.user ? item.user.nickname : ''}} (自评)
{{item.content}}
{{item.create_time}} 删除
{{item.like ? item.like.like_num : 0}} {{replyIndex == index ? '取消回复' : '回复'}}
评论
{{items.user ? items.user.nickname : '暂无昵称'}} (自评)
{{items.content}}
{{items.create_time}} 删除
{{items.like ? items.like.like_num : 0}} {{replyIndexJ == (index+'|'+indexJ) ? '取消回复' : '回复'}}
评论
目录
  • {{item}}