Springboot项目-修改密码
- 1. 找回密码
- 2. 验证短信code
- 3. 修改密码
- 4. 代码工具类
如下代码,是用户在忘记密码的情况下,通过手机号重置密码,没有用户登陆的token,关于在设置token值,在我的另一边文章有讲到,欢迎小伙伴一起来参与讨论
1. 找回密码
需求说明:
1. 获取短信验证码: 通过手机号来判断用户是否存在,生成短信验证码并且保存到redis中,设置超时时间
2. 下一步: 检查短信验证码是否正确
3. 提交: 修改密码
接收参数的form类
@Data
public class FindPasswordVo {
@Pattern(regexp = RegexUtil.PHONE, message = "请填写正确的手格式")
private String phone;
}
controller控制层
@Resource
private UserService userService;
@ApiOperation("找回密码")
@PostMapping("/findPassword")
public ResultVO<Object> findPassword(
@Valid @RequestBody FindPasswordVo findPasswordVo,
BindingResult bindingResult) {
Map<String, String> resultMap = userService.findPassword(findPasswordVo);
return ResultVO.success(resultMap);
}
service业务层
//接口抽象方法
Map<String, String> findPassword(FindPasswordVo findPasswordVo);
@Override
public Map<String, String> findPassword(FindPasswordVo findPasswordVo) {
// 验证手机号是否存在数据库中
Integer count = userMapper.selectCount(
new LambdaQueryWrapper<User>().eq(User::getPhone, findPasswordVo.getPhone()));
if (count != 1) {
throw new ResultException(ResultEnum.ACCOUNT_NOT_EXIST);
//账户不存在,可以抛出模糊的异常,手机号错误,账号密码在错误
}
HashMap<String, String> hashMap = new HashMap<>(2);
// todo 调用短信接口
String smsCode = RandomUtil.createCode();
try {
SmsUtil.sendSimpleSms(findPasswordVo.getPhone(), SmsType.SMS_CAR);
} catch (ClientException e) {
log.error("send sms error,", e);
}
// 吧短信code 放在redis里面 失效时间为5分钟
redisTemplate.opsForValue().set(FormTipsConstant.FIND_PASSWORD + findPasswordVo.getPhone(),
smsCode, 5, TimeUnit.MINUTES);
// key 到时候乣前段传过来
hashMap.put("key", findPasswordVo.getPhone());
// todo 上线关闭
hashMap.put("code", smsCode);
return hashMap;
}
2. 验证短信code
接收参数的form类
@Data
public class CheckSmsCodeVo {
@Pattern(regexp = RegexUtil.PHONE, message = "请填写正确的手格式")
private String phone;
@NotEmpty(message = FormTipsConstant.SMS_CODE_IS_NOT_EMPTY)
private String code;
}
controller控制层
@ApiOperation("验证短信code")
@PostMapping("/checkSmsCode")
public ResultVO<Object> checkSmsCode(
@Valid @RequestBody CheckSmsCodeVo checkSmsCodeVo,
BindingResult bindingResult) {
userService.checkSmsCode(checkSmsCodeVo);
return ResultVO.success();
}
service层
void checkSmsCode(CheckSmsCodeVo checkSmsCodeVo);//接口
@Override
public void checkSmsCode(CheckSmsCodeVo checkSmsCodeVo) {
String code = redisTemplate.opsForValue().get(
FormTipsConstant.FIND_PASSWORD + checkSmsCodeVo.getPhone());
if (StringUtil.isEmpty(code)) {
throw new ResultException(ResultEnum.SMS_CODE_LOSE);
}
// 验证码错误
if (!code.equals(checkSmsCodeVo.getCode())) {
throw new ResultException(ResultEnum.SMS_CODE_IS_NOT_TURE);
}
}
3. 修改密码
接收参数的form类
@ApiModel("修改密码接收类")
@Data
public class ChangePasswordVo {
@Pattern(regexp = RegexUtil.PHONE, message = FormTipsConstant.SMS_CODE_IS_NOT_EMPTY)
@ApiModelProperty("手机号码")
private String phone;
@NotEmpty(message = "密码不能为空")
@ApiModelProperty("密码")
private String password;
@NotEmpty(message = "确认密码不能为空")
@ApiModelProperty("确认密码")
private String rePassword;
}
controller控制层
@ApiOperation("修改密码")
@PostMapping("/changePassword")
public ResultVO<Object> changePassword(
@Valid @RequestBody ChangePasswordVo changePasswordVo,
BindingResult bindingResult) {
userService.changePassword(changePasswordVo);
return ResultVO.success();
}
service层
void changePassword(ChangePasswordVo changePasswordVo);
@Override
public void changePassword(ChangePasswordVo changePasswordVo) {
String code = redisTemplate.opsForValue().get(
FormTipsConstant.FIND_PASSWORD + changePasswordVo.getPhone());
if (StringUtil.isEmpty(code)) {
throw new ResultException(ResultEnum.SMS_CODE_LOSE);
}
if (!changePasswordVo.getPassword().equals(changePasswordVo.getRePassword())) {
throw new ResultException(ResultEnum.PASSWORD_NOT_SAME);
}
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getPhone, changePasswordVo.getPhone()));
if (null == user) {
throw new ResultException(ResultIf.DATA_NOT_EXISTS);
}
String encrypt = PasswordUtil.encrypt(changePasswordVo.getPassword(), user.getEncrypt());
user.setPassword(encrypt);
userMapper.updateById(user);
}
4. 代码工具类
1.1 返回结果的枚举类
@Getter
public enum ResultEnum implements ResultIf {
/**
* ===========================系统基本返回===============================
*/
SYSTEM_ERROR(-3, "系统繁忙,请稍后重试"),
ACCOUNT_EXPIRE(-2, "账号过期"),
UN_LOGIN(-1, "未登录"),
SUCCESS(0, "成功"),
ILLEGAL_REQUEST(1, "非法请求"),
VALIDATOR_ERROR(2, "验证失败"),
SQL_ERROR(3, "数据库操作失败"),
DATA_NOT_EXISTS(4, "数据不存在"),
DATA_DUPLICATE(5, "不允许添加重复数据"),
PARAMETER_EMPTY_ERROR(6, "必填参数不能为空"),
DATA_DIRTY_READ_ERROR(7, "数据异常"),
NO_HAS_JURISDICTION(8, "无权限进行此操作"),
IMAGE_CODE_ERROR(10001, "图形验证码不正确或已失效"),
DELETE_DATA_NOT_EXISTS(40001, "删除数据存在审核通过的数据"),
IMAGE_ERROR(6000, "上传图片失败"),
IMAGE_DOWNLOAD_ERROR(6001, "下载图片失败"),
IMAGE_NOT_EXISTS(6002, "图片不存在"),
IS_NOT_IMAGE(6003, "上传的不是图片或者为空"),
IMAGE_SIZE_ERROR(6004, "图片的大小超过5M"),
LOCATION_NUM_ALREADY_MAX(6005, "推荐位置数量已到6个"),
WE_CHAT_DOWNLOAD_ERROR(190003, "请点击右上角,在浏览器中打开重试"),
NO_SELECT_TARGET(190004, "请选择对应链接"),
VERSION_TOO_SMALL(200001, "版本号必须大于之前的版本号"),
/**
* ===========================登录账号信息相关===============================
*/
PHONE_INVALID(7000, "注册电话不合法"),
PHONE_IS_INVALID(7001, "手机号格式不对"),
PASSWORD_NOT_SAME(7002, "密码与确认密码不相同"),
OLD_PASSWORD_INCORRECT(7003, "原密码不正确"),
PASSWORD_SHOULD_NOT_SAME_AS_OLD(7004, "密码不能与原密码相同"),
ACCOUNT_IS_DISABLE(7005, "您的账号已不可用,请联系管理员"),
ACCOUNT_PASSWORD_NOT_EXIST(7006, "账号或密码不正确"),
ACCOUNT_NOT_EXIST(7012, "账号不存在"),
ACCOUNT_EXIST_REGISTER(7007, "账号已被注册"),
ACCESS_TOKEN_EMPTY_ERROR(7008, "access_token为空"),
OPEN_ID_EMPTY_ERROR(7009, "open_id为空"),
WECHAT_PARAMS_ERROR(7009, "微信配置参数异常"),
INVITE_CODE_ERROR(7011, "邀请码不正确"),
PASSWORD_FORMAT_ERROR(7012, "密码格式错误"),
ACCOUNT_IS_HAS(7013, "该账号已被注册"),
ACCOUNT_NO_HAS(7014, "该账号不存在"),
ACCOUNT_HAS_WECHAT(7014, "该微信已经绑定过账号"),
INVITE_CODE_SAME_AS_PHONE(7015, "不能填写自己的号码作为邀请码"),
PHONE_BIND_ANOTHER_WECHAT(7016, "手机号已绑定其他微信"),
ACCOUNT_HAS_PARENT_ALREADY(7017, "您已经绑定过上级"),
INVITATION_CODE_IS_NOT_ALLOWED_YOUR_SUBORDINATES(7018, "邀请码不允许填写自己的下级"),
ACCOUNT_ALREADY_EXIST_IN_HISTORY(7019, "此管理员账号存在历史数据,不允许添加"),
/**
* ===========================验证码===============================
*/
CODE_IS_NOT_TURE(170, "图形验证码错误"),
CODE_LOSE(171, "图形验证码已失效"),
SMS_CODE_LOSE(172, "短信验证码已失效"),
SMS_CODE_IS_NOT_TURE(173, "图形验证码错误"),
/**
* ===========================文档上传===============================
*/
DOC_UPLOAD_ERROR(2000, "上传的不是文档或者为空")
private int code;
private String msg;
ResultEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
1.2 random工具类
public class RandomUtil {
public static final int RANDOM_TYPE_UUID = 0;
public static final int RANDOM_TYPE_NUM = 1;
public static final int RANDOM_TYPE_LOW = 2;
public static final int RANDOM_TYPE_NUM_LOW = 3;
public static final int RANDOM_TYPE_UP = 4;
public static final int RANDOM_TYPE_NUM_UP = 5;
public static final int RANDOM_TYPE_LOW_UP = 6;
public static final int RANDOM_TYPE_NUM_LOW_UP = 7;
private static final String ALL_NUM = "0123456789";
private static final String LOW_CASE = "abcdefghijklmnopqrstuvwxyz";
private static final String UP_CASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final List<String> STRINGS = Arrays.asList("0123456789", "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
private static final int RANDOM_NUM_LENGTH = 11;
private static Random r = new Random();
private RandomUtil() {
}
//获取uuid
public static String uuid() {
return UUID.randomUUID().toString().replace("-", "");
}
public static String getRandom(int length, int type) {
if (type == 0) {
return uuid().substring(0, length);
} else {
StringBuilder buffer = new StringBuilder(26);
StringBuilder sb = new StringBuilder(length);
int range;
for(range = 0; range < STRINGS.size(); ++range) {
if ((type & 1 << range) == 1 << range) {
buffer.append((String)STRINGS.get(range));
}
}
range = buffer.length();
for(int i = 0; i < length; ++i) {
sb.append(buffer.charAt(r.nextInt(range)));
}
return sb.toString();
}
}
public static String getRandom() {
long l = r.nextLong();
String str = Long.toUnsignedString(l);
return fillZero(str);
}
private static String fillZero(String str) {
return str.length() >= 11 ? str.substring(0, 11) : fillZero("0" + str);
}
//创建随机的6位数
public static String createCode() {
return getRandom(6, 1);
}
public static String getRandomFileName() {
String y = TimeUtil.nowTimeInt();
return y + r.nextInt();
}
}
1.3 不变量 数据
public class FormTipsConstant {
public static final String SMS_CODE_IS_NOT_EMPTY = "请输入正确的手机号码";
/**
* ********************************redis前缀*******************************
*/
/**
* 找回密码前缀
*/
public static final String FIND_PASSWORD = "fp:";
/**
* 每天登录加pro值
*/
public static final String DAY_PRO = "dayPro:";
private FormTipsConstant() {
}
}
1.4 短信接口
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created on 17/6/7.
* 短信API产品的DEMO程序,工程中包含了一个SmsDemo类,直接通过
* 执行main函数即可体验短信产品API功能(只需要将AK替换成开通了云通信-短信产品功能的AK即可)
* 工程依赖了2个jar包(存放在工程的libs目录下)
* 1:aliyun-java-sdk-core.jar
* 2:aliyun-java-sdk-dysmsapi.jar
* <p>
* 备注:Demo工程编码采用UTF-8
* 国际短信发送请勿参照此DEMO
*/
public class SmsUtil {
/**
* 产品名称:云通信短信API产品,开发者无需替换
*/
private static final String PRODUCT = "Dysmsapi";
/**
* 产品域名,开发者无需替换
*/
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
private static final String ACCESS_KEY_ID = "LTAIUVUSXkZjvL2A";
private static final String ACCESS_KEY_SECRET = "fmnlEFHjoQu8iVNoCuucDbjHQNLoXD";
public static SendSmsResponse sendSms(String phone, String code, String smsType) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", ACCESS_KEY_ID, ACCESS_KEY_SECRET);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", PRODUCT, DOMAIN);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName("clara官方商城");
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(smsType);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam("{\"code\":\"" + code + "\"}");
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public static SendSmsResponse sendSimpleSms(String phone, String smsType) throws ClientException {
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", ACCESS_KEY_ID, ACCESS_KEY_SECRET);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", PRODUCT, DOMAIN);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName("clara官方商城");
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(smsType);
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
return sendSmsResponse;
}
public static QuerySendDetailsResponse querySendDetails(String bizId) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", ACCESS_KEY_ID, ACCESS_KEY_SECRET);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", PRODUCT, DOMAIN);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber("17398378986");
//可选-流水号
request.setBizId(bizId);
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
//hint 此处可能会抛出异常,注意catch
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
return querySendDetailsResponse;
}
}
密码工具类
1. 当我们修改密码的时候,需要给新密码加 salt 值,在存入数据库
2. 初始情况下,设置一个默认的 salt 值,存到数据库
import org.springframework.util.DigestUtils;
/**
* @author Tiger on 18-6-28 下午5:01
*/
public class PasswordUtil {
// x
public static String encrypt(String password, String salt) {
byte[] saltMd5 = DigestUtils.md5Digest(salt.getBytes());
byte[] passwordByte = DigestUtils.md5Digest(password.getBytes());
byte[] resultByte = new byte[32];
for (int i = 0; i < passwordByte.length && i < saltMd5.length; i++) {
resultByte[2 * i] = saltMd5[i];
resultByte[2 * i + 1] = passwordByte[i];
}
return DigestUtils.md5DigestAsHex(resultByte);
}
}