项目二02
1.介绍
- 参数校验
- 统一异常
- key值优化
- 发送短信
- 登录实现
2.参数校验
2.1.步骤
//参数判空
//手机号码正则
//密码是否一致
//验证码是否一致
//手机号码唯一
//用户注册
2.2.工具类
//工具类的方法都是静态的,传入的参数最好做判空处理
public class AssertUtil {
private AssertUtil(){
}
public static void hasText(String text,String msg){
if(!StringUtils.hasText(text)){
throw new LogicException(msg);
}
}
public static void isEquals(String v1,String v2,String msg){
if(v1 == null || v2==null){
throw new LogicException("传入参数必须有值");
}
if(!v1.equals(v2)){
throw new LogicException(msg);
}
}
}
2.3.参考代码
//下面抛出的异常设计到统一异常处理
public void regist(String nickname, String password, String rpassword, String phone, String verifyCode) {
//判断参数是否为null
AssertUtil.hasText(nickname, "昵称不能为空");
AssertUtil.hasText(password, "密码不能为空");
AssertUtil.hasText(rpassword, "认证密码不能为空");
AssertUtil.hasText(phone, "手机号码不能为空");
AssertUtil.hasText(verifyCode, "验证码不能为空");
//判断密码是否一致
AssertUtil.isEquals(password, rpassword, "两次密码不一致");
//手机格式是否符合格式
//手机号码是否唯一
if (this.checkPhone(phone)) {
throw new LogicException("手机号码已经被注册了");
}
//验证码
String code = userInfoRedisSerivce.getValueByKey(phone);
if (!verifyCode.equalsIgnoreCase(code)) {
throw new LogicException("验证码失效或者错误");
}
//用户注册
UserInfo userInfo = new UserInfo();
userInfo.setNickname(nickname);
userInfo.setPhone(phone);
userInfo.setPassword(password);
userInfo.setState(UserInfo.STATE_NORMAL);
userInfo.setHeadImgUrl("/images/default.jpg");
super.save(userInfo);
}
3.统一异常
异常分为主动异常(自己抛出)和系统异常
项目处理自己的主动异常,剩下的为系统异常
异常类
public class LogicException extends RuntimeException {
public LogicException(String message) {
super(message);
}
}
返回的JsonResult对象
@Setter
@Getter
@NoArgsConstructor
public class JsonResult<T> {
public static final int CODE_SUCCESS = 200;
public static final String MSG_SUCCESS = "操作成功";
public static final int CODE_NOLOGIN = 401;
public static final String MSG_NOLOGIN = "请先登录";
public static final int CODE_ERROR = 500;
public static final String MSG_ERROR = "系统异常,请联系管理员";
public static final int CODE_ERROR_PARAM = 501; //参数异常
private int code; //区分不同结果, 而不再是true或者false
private String msg;
private T data; //除了操作结果之后, 还行携带数据返回
public JsonResult(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> JsonResult success(T data){
return new JsonResult(CODE_SUCCESS, MSG_SUCCESS, data);
}
public static JsonResult success(){
return new JsonResult(CODE_SUCCESS, MSG_SUCCESS, null);
}
public static <T> JsonResult error(int code, String msg, T data){
return new JsonResult(code, msg, data);
}
public static JsonResult defaultError(){
return new JsonResult(CODE_ERROR, MSG_ERROR, null);
}
public static JsonResult noLogin() {
return new JsonResult(CODE_NOLOGIN, MSG_NOLOGIN, null);
}
}
正常情况下
@PostMapping("/regist")
//参数有手机号码和验证码,昵称,密码,确认密码
public JsonResult regist(String nickname, String password, String rpassword, String phone, String verifyCode) {
try {
userInfoService.regist(nickname, password, rpassword, phone, verifyCode);
} catch (LogicException e) {
e.printStackTrace();
//这里出的异常是主动抛出的异常
return JsonResult.error(JsonResult.CODE_ERROR_PARAM, e.getMessage(), null);
} catch (RuntimeException e) {
//这里抛出的异常就是除了主动抛出异常之外的异常
return JsonResult.defaultError();
}
return JsonResult.success();
}
优化后的异常
@PostMapping("/regist")
//参数有手机号码和验证码,昵称,密码,确认密码
public JsonResult regist(String nickname, String password, String rpassword, String phone, String verifyCode) {
userInfoService.regist(nickname, password, rpassword, phone, verifyCode);
return JsonResult.success();
}
优化思路:统一异常的思路有点类似AOP思想,但是真实实现不是AOP,因为使用注解的后置增强
@ControllerAdvice
public class CommonAdminException {
@ExceptionHandler(LogicException.class)
@ResponseBody
public Object logicExp(Exception e, HttpServletResponse resp) {
e.printStackTrace();
resp.setContentType("application/json;charset=utf-8");
return JsonResult.error(JsonResult.CODE_ERROR_PARAM, e.getMessage(), null);
}
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Object runTimeExp(Exception e, HttpServletResponse resp) {
e.printStackTrace();
resp.setContentType("application/json;charset=utf-8");
return JsonResult.defaultError();
}
}
4.key值优化
为了方便管理redis中的多个key,将key分别加上前缀和拼接字符
枚举类设计
public enum RedisKeys {
USER_LOGIN_TOKEN("user_login_token",Consts.USER_INFO_TOKEN_VAI_TIME * 60L),
REGIST_VERIFY_CODE("regist_verify_code", Consts.VERIFY_CODE_VAI_TIME * 60L);
private String preName;
private Long time;
RedisKeys() {
}
RedisKeys(String preName, Long time) {
this.preName = preName;
this.time = time;
}
//拼接完整的key
public String join(String value){
StringBuilder sb = new StringBuilder(80);
sb.append(this.preName).append(":").append(value);
return sb.toString();
}
}
使用key值
@Override
public void setRedisStringValue(String phone, String code) {
//phone="regit_code:"+phone;
String key= RedisKeys.REGIST_VERIFY_CODE.join(phone);
//键,值,时间,单位
redisTemplate.opsForValue().set(key,code, RedisKeys.REGIST_VERIFY_CODE.getTime(), TimeUnit.SECONDS);
}
@Override
public String getValueByKey(String phone) {
//String s = redisTemplate.opsForValue().get("regit_code:"+phone);
String key = RedisKeys.REGIST_VERIFY_CODE.join(phone);
return redisTemplate.opsForValue().get(key);
}
注意:
5.发送短信
5.1.介绍
短信的唯一标识
短信平台 京东万象
多个项目的交互可以通过http的方式和socket方式,spring封装了RestTemplate可以方便两个服务之间进行交互
5.2.实现
@Override
public void sendVerifyCode(String phone) {
//创建UUID
String uuid = UUID.randomUUID().toString().substring(0, 4).replaceAll("-","");
//这里少了拼接
StringBuilder stringBuilderUuid = new StringBuilder(80);
stringBuilderUuid.append("你的注册码是:").append(uuid).append("请在").append(Consts.VERIFY_CODE_VAI_TIME).append("之内使用");
//发送UUID
System.out.println(stringBuilderUuid.toString());
//发送短信
//spring封装的http请求工具类
RestTemplate template = new RestTemplate();
//地址,返回的接收类型,电话号码,验证码,标识符
String ret = template.getForObject(url,String.class,phone,uuid,appkey);
//将短信的返回信息发送到控制台中
System.out.println(ret);
if(!ret.contains("Success")){
throw new LogicException("短信发送失败");
}
//缓存管理
userInfoRedisSerivce.setRedisStringValue(phone, uuid);
}
发送短信的业务需要有依赖支持
查询短信支持的方式
除了可以将京东万象返回的东西以字符串显示之外,还可以以一个map的方式返回
如果是map的话需要注意如何获取“Success”,因为字符串可以使用contains的方式
xml同理,也是转换为字符串,判断是否存在“Success”
如果字符串中没有“Success”则会报错,很多短信会失效,所以具体看控制台打印的参数为准
5.3.路径优化
可以将参数写在配置文件中
获取资源文件的内容
@Value("${sms.url}")
private String url
内容可能要注意中括号【】,某种标签
springboot中的url中不支持中文,需要转换为ASCII 中文转ASCII
6.登录实现
6.1.传统方式
服务器通过请求对象获取用户名和密码
根据用户名和密码查询数据库是否存在,并封装成对象
对象如果存在,将对象缓存到Session中,并向前端返回成功的信息
如果为空,提示失败
6.2.前后端分离
因为Session只适用于浏览器,所以适用令牌机制(redis)
操作步骤
服务器通过请求对象获取用户名和密码
根据用户名和密码查询数据库是否存在,并封装成对象
对象如果存在,将token(UUID)和对象设置时效性缓存到redis中,并向前端返回成功的信息,成功的信息又拥有token和对象的json格式
如果为空,提示失败
代码实现
@PostMapping("/login")
public JsonResult login(String username, String password) {
//获取对象
UserInfo userInfo = userInfoService.login(username, password);
//获取Token,将token和对象数据存入redis中
String token = userInfoRedisSerivce.getTokenSaveValue(userInfo);
//返回的数据
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("token",token);
hashMap.put("user",userInfo);
return JsonResult.success(hashMap);
}
缓存实现
@Override
public String getTokenSaveValue(UserInfo userInfo) {
//创建Token
String token = UUID.randomUUID().toString().replace("-","");
String key = RedisKeys.USER_LOGIN_TOKEN.join(token);
String value = JSON.toJSONString(userInfo);
//存入缓存
redisTemplate.opsForValue().set(key,value,RedisKeys.USER_LOGIN_TOKEN.getTime());
String test01 = redisTemplate.opsForValue().get(key);
System.out.println(test01);
return token;
}
6.3.浏览器缓存数据的方式
浏览器其他操作