手机验证码登录

  • Post author:
  • Post category:其他


手机验证登录分为三个API接口,分别为:获取图片验证码、获取手机短信验证码、登录。

1.获取图片验证码:通过工具类生成图片验证码,将随机验证码的数字保存到session中,将图片验证码转为base64码放到对应的entity字段里。

2.获取手机短信验证码:判断手机号不能为空、手机号的格式、手机号在数据中必须存在,定义6位随机数做为短信验证码,先将验证码保存到redis中(60秒时效),

然后再发送到对应的手机号中。

3.登录:首先检验随机验证码、短信验证码不能为空,从session中获取随机验证码进行校验(验证码忽略大小写),

校验成功后就开始继续从session获取短信验证码并做校验,短信验证码验证成功后就开始进入到系统并修改登录次数、登录时间、获取下载数、个人信息等。

package com.chinamobile.cmss.share.controller;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.chinamobile.cmss.dto.CmssSmsLoginDto;
import com.chinamobile.cmss.share.service.ICmssSmsLoginService;
import com.web.response.ResponseDto;
import com.web.response.ResponseUtil;

/**
 * 手机短信验证登录
 * @author yaohongan
 *
 */

@RestController
@RequestMapping(value = "/smsLogin")
public class CmssSMSLoginController {

    /**
     * 日志
     */
    private final Logger log = Logger.getLogger(CmssSMSLoginController.class);

    @Autowired
    private ICmssSmsLoginService smsLoginService;

    /**
     * 获取随机验证码
     * @param code
     * @return
     */
    @GetMapping("/getValidationCode")
    public ResponseDto getValidationCode(final CmssSmsLoginDto sms, final HttpServletRequest request,
            final HttpServletResponse response) throws Exception {

        return ResponseUtil.wrapSuccess(smsLoginService.getValidationCode(sms, request, response));
    }

    /**
     * 获取短信验证码
     * @param code
     * @return
     */
    @PostMapping("/getSendSmsValidationCode")
    public ResponseDto getSendSmsValidationCode(@RequestBody final CmssSmsLoginDto sms) {

        smsLoginService.getSendSmsValidationCode(sms);
        return ResponseUtil.wrapSuccess();
    }

    /**
     * 登录
     * @param sms
     * @return
     */
    @PostMapping("/login")
    public ResponseDto smsLogin(@RequestBody final CmssSmsLoginDto sms, final HttpServletRequest request,
            final HttpServletResponse resp) {

        final Map<String, Object> returnMap = smsLoginService.login(sms, request, resp);
        return ResponseUtil.wrapSuccess(returnMap);
    }
}

service 接口层

package com.chinamobile.cmss.share.service;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.chinamobile.cmss.dto.CmssSmsLoginDto;

public interface ICmssSmsLoginService {

    /** 获取随机验证码 **/
    CmssSmsLoginDto getValidationCode(CmssSmsLoginDto sms, HttpServletRequest request, HttpServletResponse response)
            throws Exception;

    /** 获取短信验证码 **/
    void getSendSmsValidationCode(final CmssSmsLoginDto sms);

    /** 登录 **/
    Map<String, Object> login(CmssSmsLoginDto sms, final HttpServletRequest request, final HttpServletResponse resp);

    /** 登录后的操作 **/
    Map<String, Object> validateLogin(final String cryptogramMobilePhone, final HttpServletRequest request,
            final HttpServletResponse resp);
}
service 实现层

package com.chinamobile.cmss.share.service.impl;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.base.service.BaseService;
import com.base.util.mapper.JsonMapper;
import com.chinamobile.cmss.constants.CommonConstants;
import com.chinamobile.cmss.dto.CmssSmsLoginDto;
import com.chinamobile.cmss.dto.ValidateDto;
import com.chinamobile.cmss.share.dao.CmssPortalUserDao;
import com.chinamobile.cmss.share.entity.PortalUser;
import com.chinamobile.cmss.share.exception.CmssSmsLoginException;
import com.chinamobile.cmss.share.exception.LoginExceptionEnum;
import com.chinamobile.cmss.share.service.ICmssSmsLoginService;
import com.chinamobile.cmss.share.service.IPermissionService;
import com.chinamobile.cmss.share.utils.ValidateCodeUtil;
import com.share.page.buss.PortalUserPage;
import com.share.utils.ShareJedis;
import com.web.exception.BusinessException;

@Service("cmssSmsLoginService")
public class CmssSmsLoginServiceImpl extends BaseService<T> implements ICmssSmsLoginService {

    /** 日志 **/
    private final Logger log = Logger.getLogger(CmssSmsLoginServiceImpl.class);

    @Resource
    private ShareJedis shareJedis;

    @Value("${smsCodePrescription}")
    private Integer smsCodePrescription;

    @Autowired
    private CmssPortalUserDao<T> dao;

    @Autowired
    private CmssBatchSmsService batchSmsService;

    @Autowired
    private CmssPortalUserService<PortalUser> cmssPortalUserService;

    @Autowired
    private IPermissionService permissionService;

    // 用户下载总次数redis键值前缀
    // redis里key:userDownloadTimes:pdf:${userId},userId为当前用户登录ID
    private final static String KEY_PREFIX = "userDownloadTimes:";

    @Override
    public CmssPortalUserDao<T> getDao() {

        return dao;
    }

    /**
     * 获取随机验证码
     * @throws IOException
     */
    @Override
    public CmssSmsLoginDto getValidationCode(final CmssSmsLoginDto sms, final HttpServletRequest request,
            final HttpServletResponse response) throws Exception {

        final HttpSession session = request.getSession();
        // 直接调用静态方法,返回验证码对象
        final ValidateDto v = ValidateCodeUtil.getRandomCode();
        if (v != null) {
            // 将验证码值保存session
            session.setAttribute("validate", v.getValue());
            log.info("验证码是:" + v.getValue());
            // 保存图片二维码的base64码
            sms.setCode(v.getBase64Str());
        }
        return sms;
    }

    /**
     * 获取短信验证码
     */
    @Override
    public void getSendSmsValidationCode(final CmssSmsLoginDto sms) {

        // 手机号码不能为空
        if (StringUtils.isEmpty(sms.getPhone())) {
            throw new BusinessException(CmssSmsLoginException.PHONE_EMPTY);
        }
        // 判断手机号格式
        if (!sms.getPhone().matches("^1\\d{10}$")) {
            throw new BusinessException(CmssSmsLoginException.PHONE_EXIST);
        }
        // 校验手机号是否存在
        // 去数据库查询手机号
        final PortalUserPage portalUser = new PortalUserPage();
        portalUser.setPhone(sms.getPhone());
        final List<T> portalUserList = dao.queryByList(portalUser);
        if (portalUserList.size() == 0) {
            throw new BusinessException(CmssSmsLoginException.ACCOUNT_NOT_EXIST);
        }

        final String phone = sms.getPhone();
        sms.setSmsCode(String.valueOf((new Random().nextInt(899999) + 100000)));
        // 将短信验证码存储到redis,时效是1分钟
        shareJedis.setex(phone, sms.getSmsCode(), smsCodePrescription);
        // 发送手机验证码
        batchSmsService.sendSmsValidationCode(phone, sms.getSmsCode());
        log.info("短信验证发送成功,发送时间{" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "},手机号{"
                + sms.getPhone() + "},验证码{" + sms.getSmsCode() + "}");

    }

    /**
     * 短信验证登录
     */
    @Override
    public Map<String, Object> login(final CmssSmsLoginDto sms, final HttpServletRequest request,
            final HttpServletResponse resp) {

        // 短信验证码不能为空
        if (StringUtils.isEmpty(sms.getSmsCode())) {
            throw new BusinessException(CmssSmsLoginException.SMS_AUTHENTICATION_CODE_NOT_EXISTS);
        }
        // 随机验证码不能为空
        if (StringUtils.isEmpty(sms.getCode())) {
            throw new BusinessException(CmssSmsLoginException.VERIFICATION_CODE_EMPTY);
        }
        final HttpSession session = request.getSession();
        final String sessionValidate = (String) session.getAttribute("validate");
        // 验证随机验证码
        // 忽略验证码大小写
        if (!StringUtils.equalsIgnoreCase(sms.getCode(), sessionValidate)) {
            throw new BusinessException(CmssSmsLoginException.VERIFICATION_CODE_ERROR);
        }
        // 验证短信验证码
        // 根据手机号码取短信验证码
        final String smsCode = shareJedis.get(sms.getPhone());
        // 如果验证码为空就是超时
        if (StringUtils.isEmpty(smsCode)) {
            throw new BusinessException(CmssSmsLoginException.SMS_AUTHENTICATION_CODE_OVERTIME);
        }
        // 校验短信验证码是否匹配
        if (!smsCode.equals(sms.getSmsCode())) {
            throw new BusinessException(CmssSmsLoginException.SMS_AUTHENTICATION_CODE_ERROR);
        }
        String cryptogramMobilePhone = "";
        PortalUser pu = new PortalUser();
        final PortalUserPage pup = new PortalUserPage();
        pup.setPhone(sms.getPhone());
        final List<PortalUser> ls = cmssPortalUserService.getUserByPhone(pup);
        if (ls != null && ls.size() > 0) {
            pu = ls.get(0);
            if (CommonConstants.PORTAL_USER_STATUS.UNCHECKED_USER_STATUS.equals(pu.getStatus())) {

                throw new BusinessException(LoginExceptionEnum.UNCHECKED_USER);
            }
            if (CommonConstants.PORTAL_USER_STATUS.UNAVALIDATE.equals(pu.getStatus())) {

                throw new BusinessException(LoginExceptionEnum.UNAVALIDATE);
            }
            if (CommonConstants.PORTAL_USER_STATUS.AUTH_FAILED.equals(pu.getStatus())) {

                throw new BusinessException(LoginExceptionEnum.AUTH_FAILED);
            }
            cryptogramMobilePhone = pu.getEncryptPhone();
            shareJedis.setPortalUserInfo(cryptogramMobilePhone, pu);
        }
        final Map<String, Object> returnMap = validateLogin(cryptogramMobilePhone, request, resp);
        return returnMap;
    }

    /**
     * 登录后的操作
     * @param cryptogramMobilePhone
     * @param request
     * @param resp
     * @return
     */
    @Override
    public Map<String, Object> validateLogin(final String cryptogramMobilePhone, final HttpServletRequest request,
            final HttpServletResponse resp) {

        final Map<String, Object> returnMap = new HashMap<>();

        final PortalUser currentUser = shareJedis.getPortalUserInfo(PortalUser.class, cryptogramMobilePhone);
        log.info("long in currentUser : ========" + currentUser);

        if (null != currentUser) {
            // 取用户应用渠道
            final String uuid = UUID.randomUUID().toString().replace("-", "");
            request.getSession(true).setAttribute("JSESSIONID", uuid);
            // 登录后,对用户进行积分计算,存入缓存
            shareJedis.portalUserLoginWithMsg(currentUser.getId().longValue());
            request.getSession(true).setAttribute("session_user", currentUser);
            // 将用户应用渠道放到head中
            // resp.setHeader("channel_code", channelCode);
            // 将用户记录放到session中,并放到head中
            resp.setHeader("User-Info", new JsonMapper().toJson(currentUser));
            cmssPortalUserService.updateLoginInfo(currentUser);
            if (currentUser.getLoginCount() != null) {
                currentUser.setLoginCount(currentUser.getLoginCount() + 1);
            } else {
                currentUser.setLoginCount(1);
            }
            final String index = cmssPortalUserService.getUserIndex(currentUser.getId());
            currentUser.setIndex(index);
            returnMap.put("userInfo", currentUser);
            final Integer permission = permissionService.getPermissionLevel(currentUser);
            returnMap.put("permission", permission);
            returnMap.put("provProdTitle",
                    permissionService.getProvProductTitle(permission, currentUser.getCompanyId(), currentUser));
            returnMap.put("companyId", currentUser.getCompanyId());

            final String redisKey = KEY_PREFIX + currentUser.getId();
            if (shareJedis.exists(redisKey)) {
                returnMap.put("userDownloadTimes", shareJedis.get(redisKey));
            } else {
                returnMap.put("userDownloadTimes", 0);
            }
        } else {
            log.info("Login error [" + cryptogramMobilePhone + "]");
            throw new BusinessException(LoginExceptionEnum.ACCOUNT_NOT_EXIST);
        }

        return returnMap;

    }

}

图片验证码工具类

package com.chinamobile.cmss.share.utils;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

import org.apache.commons.lang3.RandomUtils;

import com.alibaba.druid.util.Base64;
import com.chinamobile.cmss.dto.ValidateDto;

/**
 *
 * @ClassName: ValidateCodeUtil
 * @Description: 验证码生成工具类
 * @author yaohongan
 */
public class ValidateCodeUtil {

    private static ValidateDto validateDto = null; // 验证码类,用于最后返回此对象,包含验证码图片base64和真值

    private static Random random = new Random(); // 随机类,用于生成随机参数

    private static String randString = "0123456789abcdefghijkmnpqrtyABCDEFGHIJLMNQRTY";// 随机生成字符串的取值范围

    private static int width = 80; // 图片宽度

    private static int height = 34; // 图片高度

    private static int StringNum = 4; // 字符的数量

    private static int lineSize = 40; // 干扰线数量

    // 将构造函数私有化 禁止new创建
    private ValidateCodeUtil() {

        super();
    }

    /**
     * 获取随机字符,并返回字符的String格式
     * @param index (指定位置)
     * @return
     */
    private static String getRandomChar(final int index) {

        // 获取指定位置index的字符,并转换成字符串表示形式
        return String.valueOf(randString.charAt(index));
    }

    /**
     * 获取随机指定区间的随机数
     * @param min (指定最小数)
     * @param max (指定最大数)
     * @return
     */
    private static int getRandomNum(final int min, final int max) {

        return RandomUtils.nextInt(min, max);
    }

    /**
     * 获得字体
     * @return
     */
    private static Font getFont() {

        return new Font("Fixedsys", Font.CENTER_BASELINE, 25); // 名称、样式、磅值
    }

    /**
     * 获得颜色
     * @param fc
     * @param bc
     * @return
     */
    private static Color getRandColor(int frontColor, int backColor) {

        if (frontColor > 255) {
            frontColor = 255;
        }
        if (backColor > 255) {
            backColor = 255;
        }

        final int red = frontColor + random.nextInt(backColor - frontColor - 16);
        final int green = frontColor + random.nextInt(backColor - frontColor - 14);
        final int blue = frontColor + random.nextInt(backColor - frontColor - 18);
        return new Color(red, green, blue);
    }

    /**
     * 绘制字符串,返回绘制的字符串
     * @param g
     * @param randomString
     * @param i
     * @return
     */
    private static String drawString(final Graphics g, String randomString, final int i) {

        final Graphics2D g2d = (Graphics2D) g;
        g2d.setFont(getFont()); // 设置字体
        g2d.setColor(new Color(random.nextFloat(), random.nextFloat(), random.nextFloat()));// 设置颜色
        final String randChar = String.valueOf(getRandomChar(random.nextInt(randString.length())));
        randomString += randChar; // 组装
        final int rot = getRandomNum(5, 10);
        g2d.translate(random.nextInt(3), random.nextInt(3));
        g2d.rotate(rot * Math.PI / 180);
        g2d.drawString(randChar, 13 * i, 20);
        g2d.rotate(-rot * Math.PI / 180);
        return randomString;
    }

    /**
     * 绘制干扰线
     * @param g
     */
    private static void drawLine(final Graphics g) {

        // 起点(x,y) 偏移量x1、y1
        final int x = random.nextInt(width);
        final int y = random.nextInt(height);
        final int xl = random.nextInt(13);
        final int yl = random.nextInt(15);
        g.setColor(new Color(random.nextFloat(), random.nextFloat(), random.nextFloat()));
        g.drawLine(x, y, x + xl, y + yl);
    }

    /**
     *
     * @MethodName: getRandomCode
     * @Description: 生成Base64图片验证码
     * @param key
     * @return String 返回base64
     */
    public static ValidateDto getRandomCode() {

        validateDto = validateDto == null ? new ValidateDto() : validateDto;

        // BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        final Graphics g = image.getGraphics();// 获得BufferedImage对象的Graphics对象
        g.fillRect(0, 0, width, height);// 填充矩形
        g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));// 设置字体
        g.setColor(getRandColor(110, 133));// 设置颜色
        // 绘制干扰线
        for (int i = 0; i <= lineSize; i++) {
            drawLine(g);
        }
        // 绘制字符
        String randomString = "";
        for (int i = 1; i <= StringNum; i++) {
            randomString = drawString(g, randomString, i);
            validateDto.setValue(randomString);
        }

        g.dispose();// 释放绘图资源
        ByteArrayOutputStream bs = null;
        try {
            bs = new ByteArrayOutputStream();
            ImageIO.write(image, "png", bs);// 将绘制得图片输出到流
            final String imgsrc = Base64.byteArrayToBase64(bs.toByteArray());
            validateDto.setBase64Str(imgsrc);
        } catch (final Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bs.close();
            } catch (final IOException e) {
                e.printStackTrace();
            } finally {
                bs = null;
            }
        }
        return validateDto;
    }

}

验证码entity

package com.chinamobile.cmss.dto;

import java.io.Serializable;

/**
 *
 * @ClassName: ValidateDto
 * @Description: 验证码类
 * @author yaohongan
 */
public class ValidateDto implements Serializable {

    private static final long serialVersionUID = 1L;

    private String Base64Str; // Base64 值

    private String value; // 验证码值

    public String getBase64Str() {

        return Base64Str;
    }

    public void setBase64Str(final String base64Str) {

        Base64Str = base64Str;
    }

    public String getValue() {

        return value;
    }

    public void setValue(final String value) {

        this.value = value;
    }
}

发送短信代码

package com.chinamobile.cmss.share.service.impl;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletContext;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import com.base.util.mapper.JsonMapper;
import com.chinamobile.cmss.share.entity.CmssDict;
import com.chinamobile.cmss.share.entity.CmssLogAccess;
import com.chinamobile.cmss.share.entity.CmssOperationsUser;
import com.chinamobile.cmss.share.entity.CmssSmsRedisEntity;
import com.chinamobile.cmss.share.entity.PortalUser;
import com.share.utils.ShareJedis;

import freemarker.template.Configuration;
import freemarker.template.Template;
/**
 *
 * @ClassName: BatchSmsService
 * @Description: 批量发送短信
 * @author zhangle
 *
 */
@SuppressWarnings("boxing")
@Service("cmssBatchSmsService")
public class CmssBatchSmsService {
    /**
     * 发送短信验证码
     * @param portalUser
     */
    public void sendSmsValidationCode(final String phone, final String smsCode) {

        final WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        final ServletContext servletContext = webApplicationContext.getServletContext();
        final Configuration cfg = new Configuration();

        cfg.setServletContextForTemplateLoading(servletContext, "/WEB-INF/templates");

        final Map<String, Object> content = new HashMap<String, Object>(16);

        content.put("smsCode", smsCode);
        content.put("smsTime", smsTime);

        Template t;
        try {
            t = cfg.getTemplate("new_user_sms.ftl", "utf-8");
            final StringWriter writer = new StringWriter();

            t.process(content, writer);

            final String smsContent = writer.toString();
            // log.info("=======================smsContent: "+smsContent);
            sendShortMessage(smsContent, Arrays.asList(new String[] { phone }));

        } catch (final Exception e) {
            log.info("用户手机号为[" + phone + "]发送短信失败!", e);
        }

    }
}