手机验证登录分为三个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);
}
}
}