7. 用户-注册-前端页面
(开发笔记:无)
完成后,当用户在登录页面输入用户名、密码,并点击“立即注册”按钮时,就会触发所绑定的事件,执行了$.ajax()
函数,该函数执行时,会将表单中的数据进行序列化(组织成服务器可以接收的格式),根据type
方式向url
指定的路径提交请求!
服务器端的Tomcat会根据DispatcherServlet
的配置(在SpringBoot项目中,映射的路径是/*
),接收到该请求,并转由UserController
的reg()
方法进行处理,在该方法内部,是调用了Service对象的reg()
方法进行处理的,当前项目中,Service对象其实就是UserServiceImpl
类的对象(因为这个在组件扫描的包中,并且添加了@Service
注解,所以,这个类的对象是由Spring框架创建并自动装配到UserController
的属性中的)!
在UserServiceImpl
的reg()
方法中就实现了注册过程,相关的数据操作(查询、插入)都是由基于MyBatis框架开发的持久层来实现的,整个reg()
执行完毕后,没有返回结果,则在UserController
中关于userService
的调用也就结束,即userService.reg()
执行完毕!
接下来,UserController
中就返回了JSON数据(代码为return new JsonResult<>(OK);
,是由Jackson框架将这行代码所创建的JsonResult
对象转换为了JSON格式的字符串),则客户端的$.ajax()
函数就会收到响应的结果,这个结果就是服务器端返回JSON字符串,由于响应是成功(HTTP响应码是200)的,则$.ajax()
函数的success
配置就会被触发,所以,就回调了success
对应的函数,该函数的参数就是服务器端响应的JSON结果!
8. 用户-登录-持久层
在处理登录时,需要执行增删改查操作应该只有“查询”操作,客户端会提交用户名和密码,应该先只根据用户名查询用户数据,然后,取出其中的盐值,基于客户端提交的密码原文与盐值执行加密,得到相应密文,将密文与查询结果中的密码进行对比,就可以判断密码是否正确!
那么,需要执行的SQL语句大致是:
select * from t_user where username=?
在开发“注册”功能时,就已经实现了以上查询功能!所以,此次无需开发持久层!
9. 用户-登录-业务层
应该先创建处理“登录”过程中可能出现的错误对应的异常!例如,当用户名不存在时,可能抛出异常,则在cn.tedu.store.service.ex
包中创建UserNotFoundException
异常类,它应该继承自ServiceException
;另外,当密码错误时,也会抛出异常,则在同样的包中创建PasswordNotMatchException
异常类,也是继承自ServiceException
的!
在IUserService
接口中添加抽象方法:
User login(String username, String password);
然后,在UserServiceImpl
中重写以上抽象方法:
public User login(String username, String password) {
// 根据参数username,调用userMapper.findByUsername()方法执行查询
// 判断查询结果是否为null
// 是:抛出UserNotFoundException
// 判断查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException
// 从查询结果中取出盐值
// 基于参数password与盐值执行加密,得到密文
// 判断以上得到的密文与查询结果中的password是否不一致
// 是:抛出PasswordNotMatchException
// 创建新的User对象
// 将查询结果中的uid、username、avatar的值封装到新创建的对象中
// 返回新创建的对象
return null;
}
具体代码为:
@Override
public User login(String username, String password) {
// 输出日志
System.err.println("UserServiceImpl.login()");
System.err.println("\tusername=" + username);
System.err.println("\tpassword=" + password);
// 根据参数username,调用userMapper.findByUsername()方法执行查询
User result = userMapper.findByUsername(username);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出UserNotFoundException
throw new UserNotFoundException(
"登录失败!用户数据不存在!");
}
// 判断查询结果中的isDelete是否为1
if (result.getIsDelete() == 1) {
// 是:抛出UserNotFoundException
throw new UserNotFoundException(
"登录失败!用户数据不存在!");
}
// 从查询结果中取出盐值
String salt = result.getSalt();
// 基于参数password与盐值执行加密,得到密文
String md5Password = getMd5Password(password, salt);
// 判断以上得到的密文与查询结果中的password是否不一致
System.err.println("\t数据库中的密码:" + result.getPassword());
if (!md5Password.equals(result.getPassword())) {
// 是:抛出PasswordNotMatchException
throw new PasswordNotMatchException(
"登录失败!密码错误!");
}
// 创建新的User对象
User user = new User();
// 将查询结果中的uid、username、avatar的值封装到新创建的对象中
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
// 返回新创建的对象
return user;
}
最后,在UserServiceTests
中测试以上新开发的功能是否正确:
@Test
public void login() {
try {
String username = "rootx";
String password = "1234x";
User user = service.login(username, password);
System.err.println("OK. " + user);
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
10. 用户-登录-控制器层
首先,此次处理“登录”时,在业务层抛出了2种新的异常,则需要在GlobalExcpetionHandler
中进行处理!
然后,在UserController
中添加处理“登录”的方法:
// http://localhost:8080/users/login?username=root&password=1234
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
User data = userService.login(username, password);
session.setAttribute("uid", data.getUid());
session.setAttribute("username", data.getUsername());
return new JsonResult<>(OK, data);
}
11. 用户-登录-前端页面
12. 用户-修改密码-持久层
(a) 规划所需要执行的SQL语句
需要修改密码时,执行的是“更新密码字段的值”的操作,需要执行的SQL语句大致是:
update t_user set password=?, modified_user=?, modified_time=? where uid=?
通常,在执行增、删、改类型的操作之前,都可能有一些相关的检查,也就是需要执行某些查询类型的操作,用于检查数据的访问是否合理!例如应该检查需要修改密码的这条用户数据是否存在,可以使用的SQL语句大致是:
select * from t_user where uid=?
(b) 接口与抽象方法
在UserMapper
接口中添加以上规则的2个功能的抽象方法:
/**
* 修改某用户的密码
* @param uid 用户的id
* @param password 新密码
* @param modifiedUser 修改执行人
* @param modifiedTime 修改时间
* @return 受影响的行数
*/
Integer updatePasswordByUid(
@Param("uid") Integer uid,
@Param("password") String password,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
/**
* 根据用户id查询用户数据详情
* @param uid 用户id
* @return 匹配的用户数据,如果没有匹配的数据,则返回null
*/
User findByUid(Integer uid);
(c) 配置映射
在UserMapper.xml中配置以上2个抽象方法的映射:
<!-- 修改某用户的密码 -->
<!-- Integer updatePasswordByUid(
@Param("uid") Integer uid,
@Param("password") String password,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime) -->
<update id="updatePasswordByUid">
UPDATE
t_user
SET
password=#{password},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
uid=#{uid}
</update>
<!-- 根据用户id查询用户数据详情 -->
<!-- User findByUid(Integer uid) -->
<select id="findByUid"
resultMap="UserEntityMap">
SELECT
*
FROM
t_user
WHERE
uid=#{uid}
</select>
在UserMapperTests
中添加2个单元测试:
@Test
public void updatePasswordByUid() {
Integer uid = 16;
String password = "1234";
String modifiedUser = "密码管理员";
Date modifiedTime = new Date();
Integer rows = mapper.updatePasswordByUid(uid, password, modifiedUser, modifiedTime);
System.err.println("rows=" + rows);
}
@Test
public void findByUid() {
Integer uid = 16000;
User result = mapper.findByUid(uid);
System.err.println(result);
}
13. 用户-修改密码-业务层
(a) 规划可能出现的异常
最终需要执行的是更新密码操作,则可能出现更新失败的问题,则应该抛出UpdateException
;
在执行更新之前,应该检查用户数据是否存在,如果用户数据不存在,则抛出UserNotFoundException
;
并且,继续检查用户数据状态是否正确,例如isDelete
是否为0
,否则,将抛出UserNotFoundException
;
在执行更新密码之前,还应该检查原密码是否正确,如果不匹配,则抛出PasswordNotMatchException
。
所以,需要在cn.tedu.store.service.ex
包中创建UpdateException
。
(b) 业务接口及抽象方法
在IUserService
接口中添加抽象方法:
void changePassword(Integer uid, String oldPassword, String newPassword, String username);
(c) 实现抽象方法
在UserServiceImpl
类中实现以上抽象方法:
public void changePassword(Integer uid, String oldPassword, String newPassword, String username) {
// 日志:输出原密码,新密码
// 基于参数uid调用userMapper的findByUid()查询用户数据
// 判断查询结果是否为null
// 是:UserNotFoundException
// 判断查询结果中的isDelete是否为1
// 是:UserNotFoundException
// 从查询结果中取出盐值
// 日志:“将原密码执行加密”
// 将参数oldPassword结合盐值执行加密,得到oldMd5Password
// 日志:输出查询结果中的password
// 判断oldMd5Password与查询结果中的password是否不一致
// 是:PasswordNotMatchException
// 日志:“将新密码执行加密”
// 将参数newPassword结合盐值执行加密,得到newMd5Password
// 调用userMapper的updatePasswordByUid()执行更新密码,并获取返回的受影响的行数
// 判断受影响的行数是否不为1
// 是:UpdateException
}
完成后,在UserServiceTests
中编写单元测试:
xxx
14. 用户-修改密码-控制器层
15. 用户-修改密码-前端页面
16. 用户-修改资料-持久层
17. 用户-修改资料-业务层
18. 用户-修改资料-控制器层
19. 用户-修改资料-前端页面
------------------------------------------------
附1:使用SpringMVC框架统一处理异常【补充】
添加了@ExceptionHandler
注解的处理异常的方法,只能作用于当前控制器类,也就是当前控制器中处理请求时出现的异常才可以被处理,其它控制器处理请求时出现的异常并不会被处理!
可以将处理异常的方法写在控制器类的基类中,则每个控制器都相当于有这个方法,就可以统一处理异常了!这个基类的声明必须使用public
权限修饰,而并不可以使用默认的访问权限!另外,这个基类可以声明为抽象的!
另外,也可以将处理异常的类添加@ControllerAdvice
注解,并在处理异常的方法之前添加@ResponseBody
注解,或者,直接在类之前添加@RestControllerAdvice
并在方法之前不添加@ResponseBody
注解,就可以使得当前类是SpringMVC框架的全局化的类,当前类中处理异常的做法也是全局化的,所以,无论是哪个控制器类的方法处理异常时出现异常,都会由这个全局化类中的处理方式进行处理!注意:@ControllerAdvice/@RestControllerAdvice这2个注解在SpringMVC框架中默认不可用,必须另外配置,在SpringBoot框架中是可以直接使用的!
关于统一处理异常时,使用的@ExceptionHandler
注解,还可以配置注解参数,例如配置为:
@ExceptionHandler(ServiceException.class)
则表示只有ServiceException
及其子孙类异常才会被接下来的方法进行处理,而其它类型的异常是不被接下的方法处理的!
该注解有参数是数组类型的,也可以使用大括号{}
框住多个异常的类型!