用户登录
1 用户-登录-持久层
学习视频地址【SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战_哔哩哔哩_bilibili
1.1 规划需要执行的SQL语句
用户登录功能需要执行的SQL语句是根据用户名查询用户数据,再判断密码是否正确。SQL语句大致是:
SELECT * FROM t_user WHERE username=?
说明:以上SQL语句对应的后台开发已经完成,无需再次开发。
1.2 接口与抽象方法
说明:无需再次开发。
1.3 配置SQL映射
说明:无需再次开发。
2 用户-登录-业务层
2.1 规划异常
1.如果用户名不存在则登录失败,抛出com.cy.store.service.ex.UserNotFoundException异常,并从父类生成子类的五个构造方法。
package com.cy.store.service.ex;
/** 用户数据不存在的异常 */
public class UserNotFoundException extends ServiceException {
// Override Methods...
}
2.如果用户的isDelete字段的值为1,则表示当前用户数据被标记为“已删除”,需进行登录失败操作同时抛出UserNotFoundException。
3.如果密码错误则进行登录失败操作,同时抛出com.cy.store.service.ex.PasswordNotMatchException异常。
package com.cy.store.service.ex;
/** 密码验证失败的异常 */
public class PasswordNotMatchException extends ServiceException {
// Override Methods...
}
4.创建以上UserNotFoundException和PasswordNotMatchException异常类,以上异常类应继承自ServiceException类。
2.2 接口与抽象方法
在IUserService接口中添加登录功能的抽象方法。
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录成功的用户数据
*/
User login(String username, String password);
当登录成功后需要获取该用户的id,以便于后续识别该用户的身份,并且还需要获取该用户的用户名、头像等数据,用于显示在软件的界面中,需使用可以封装用于id、用户名和头像的数据的类型来作为登录方法的返回值类型。
2.3 实现抽象方法
1.在UserServiceImpl类中添加login(String username, String password)方法并分析业务逻辑。
@Override
public User login(String username, String password) {
// 调用userMapper的findByUsername()方法,根据参数username查询用户数据
// 判断查询结果是否为null
// 是:抛出UserNotFoundException异常
// 判断查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException异常
// 从查询结果中获取盐值
// 调用getMd5Password()方法,将参数password和salt结合起来进行加密
// 判断查询结果中的密码,与以上加密得到的密码是否不一致
// 是:抛出PasswordNotMatchException异常
// 创建新的User对象
// 将查询结果中的uid、username、avatar封装到新的user对象中
// 返回新的user对象
return null;
}
2.login(String username, String password)方法中代码的具体实现。
@Override
public User login(String username, String password) {
// 调用userMapper的findByUsername()方法,根据参数username查询用户数据
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();
// 调用getMd5Password()方法,将参数password和salt结合起来进行加密
String md5Password = getMd5Password(password, salt);
// 判断查询结果中的密码,与以上加密得到的密码是否不一致
if (!result.getPassword().equals(md5Password)) {
// 是:抛出PasswordNotMatchException异常
throw new PasswordNotMatchException("密码验证失败的错误");
}
// 创建新的User对象
User user = new User();
// 将查询结果中的uid、username、avatar封装到新的user对象中
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
// 返回新的user对象
return user;
}
3.完成后在UserServiceTests中编写并完成单元测试。
@Test
public void login() {
try {
String username = "lower";
String password = "123456";
User user = iUserService.login(username, password);
System.out.println("登录成功!" + user);
} catch (ServiceException e) {
System.out.println("登录失败!" + e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
注意:不要使用错误的数据尝试登录,例如早期通过持久层测试新增用户的数据,将这些数据从表中删除。
3 用户-登录-控制器
3.1 处理异常
处理用户登录功能时,在业务层抛出了UserNotFoundException和PasswordNotMatchException异常,而这两个异常均未被处理过。则应在BaseController类的处理异常的方法中,添加这两个分支进行处理。
@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleException(Throwable e) {
JsonResult<Void> result = new JsonResult<Void>(e);
if (e instanceof UsernameDuplicateException) {
result.setState(4000);
} else if (e instanceof UserNotFoundException) {
result.setState(4001);
} else if (e instanceof PasswordNotMatchException) {
result.setState(4002);
} else if (e instanceof InsertException) {
result.setState(5000);
}
return result;
}
3.2 设计请求
设计用户提交的请求,并设计响应的方式:
请求路径:/users/login 请求参数:String username, String password 请求类型:POST 响应结果:JsonResult<User>
3.3 处理请求
1.在UserController类中添加处理登录请求的login(String username, String password)方法。
@RequestMapping("login")
public JsonResult<User> login(String username, String password) {
// 调用业务对象的方法执行登录,并获取返回值
// 将以上返回值和状态码OK封装到响应结果中并返回
return null;
}
2.处理登录请求的login(String username, String password)方法代码具体实现。
@RequestMapping("login")
public JsonResult<User> login(String username, String password) {
// 调用业务对象的方法执行登录,并获取返回值
User data = userService.login(username, password);
// 将以上返回值和状态码OK封装到响应结果中并返回
return new JsonResult<User>(OK, data);
}
4.完成后启动项目,访问http://localhost:8080/users/login?username=Tom&password=1234请求进行登录。
4 用户-登录-前端页面
1.在login.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。
<script type="text/javascript">
$("#btn-login").click(function() {
$.ajax({
url: "/users/login",
type: "POST",
data: $("#form-login").serialize(),
dataType: "json",
success: function(json) {
if (json.state == 200) {
alert("登录成功!");
location.href = "index.html";
} else {
alert("登录失败!" + json.message);
}
}
});
});
</script>
2.完成后启动项目,打开浏览器访问http://localhost:8080/web/login.html页面并进行登录。
拦截器
在Spring MVC中拦截请求是通过处理器拦截器HandlerInterceptor来实现的,它拦截的目标是请求的地址。在Spring MVC中定义一个拦截器,需要实现HandlerInterceptor接口。
1 HandlerInterceptor
1.1 preHandle()方法
该方法将在请求处理之前被调用。SpringMVC中的Interceptor是链式的调用,在一个应用或一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle()方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值类型,当返回false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值true时,就会继续调用下一个Interceptor的preHandle方法,如果已经是最后一个Interceptor的时,就会调用当前请求的Controller方法。
1.2 postHandle()方法
该方法将在当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作。postHandle方法被调用的方向跟preHandle是相反的,也就是说先声明的Interceptor的postHandle方法反而会后执行。如果当前Interceptor的preHandle()方法返回值为false,则此方法不会被调用。
1.3 afterCompletion()方法
该方法将在整个当前请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作。如果当前Interceptor的preHandle()方法返回值为false,则此方法不会被调用。
2 WebMvcConfigurer
在SpringBoot项目中,如果想要自定义一些Interceptor、ViewResolver、MessageConverter,该如何实现呢?在SpringBoot 1.5版本都是靠重写WebMvcConfigurerAdapter类中的方法来添加自定义拦截器、视图解析器、消息转换器等。而在SpringBoot 2.0版本之后,该类被标记为@Deprecated。因此我们只能靠实现WebMvcConfigurer接口来实现。
WebMvcConfigurer接口中的核心方法之一addInterceptors(InterceptorRegistry registry)方法表示添加拦截器。主要用于进行用户登录状态的拦截,日志的拦截等。
-
addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
-
addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截
-
excludePathPatterns:用于设置不需要拦截的过滤规则
public interface WebMvcConfigurer {
// ...
default void addInterceptors(InterceptorRegistry registry) {
}
}
3 项目添加拦截器功能
1.分析:项目中很多操作都是需要先登录才可以执行的,如果在每个请求处理之前都编写代码检查Session中有没有登录信息,是不现实的。所以应使用拦截器解决该问题。
2.创建拦截器类com.cy.store.interceptor.LoginInterceptor,并实现org.springframework.web.servlet.HandlerInterceptor接口。
package com.cy.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** 定义处理器拦截器 */
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("uid") == null) {
response.sendRedirect("/web/login.html");
return false;
}
return true;
}
}
3.创建LoginInterceptorConfigurer拦截器的配置类并实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口,配置类需要添加@Configruation注解修饰。
package com.cy.store.config;
import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/** 注册处理器拦截器 */
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
/** 拦截器配置 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
// 白名单
List<String> patterns = new ArrayList<String>();
patterns.add("/bootstrap3/**");
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/index.html");
patterns.add("/web/product.html");
patterns.add("/users/reg");
patterns.add("/users/login");
patterns.add("/districts/**");
patterns.add("/products/**");
// 通过注册工具添加拦截器
registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns(patterns);
}
}
会话
1.重新构建login()方法,登录成功后将uid和username存入到HttpSession对象中。
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
// 调用业务对象的方法执行登录,并获取返回值
User data = userService.login(username, password);
//登录成功后,将uid和username存入到HttpSession中
session.setAttribute("uid", data.getUid());
session.setAttribute("username", data.getUsername());
// System.out.println("Session中的uid=" + getUidFromSession(session));
// System.out.println("Session中的username=" + getUsernameFromSession(session));
// 将以上返回值和状态码OK封装到响应结果中并返回
return new JsonResult<User>(OK, data);
}
2.在父类BaseController中添加从HttpSession对象中获取uid和username的方法,以便于后续快捷的获取这两个属性的值。
/**
* 从HttpSession对象中获取uid
* @param session HttpSession对象
* @return 当前登录的用户的id
*/
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 从HttpSession对象中获取用户名
* @param session HttpSession对象
* @return 当前登录的用户名
*/
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
修改密码
1 用户-修改密码-持久层
1.1 规划需要执行的SQL语句
用户修改密码时需要执行的SQL语句大致是:
UPDATE t_user SET password=?, modified_user=?, modified_time=? WHERE uid=?
在执行修改密码之前,还应检查用户数据是否存在、并检查用户数据是否被标记为“已删除”、并检查原密码是否正确,这些检查都可以通过查询用户数据来辅助完成:
SELECT * FROM t_user WHERE uid=?
1.2 接口与抽象方法
在UserMapper接口添加updatePasswordByUid(Integer uid,String password,String modifiedUser,Date modifiedTime)抽象方法。
用注解来简化xml配置时,@Param注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中。@Param("参数名")注解中的参数名需要和sql语句中的#{参数名}的参数名保持一致。
/**
* 根据uid更新用户的密码
* @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);
1.3 配置SQL映射
1.在UserMapper.xml中配置updatePasswordByUid()、findByUid()抽象方法的映射。
<!-- 根据uid更新用户的密码:
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>
2.在UserMapperTests中编写并执行单元测试。
@Test
public void updatePasswordByUid() {
Integer uid = 7;
String password = "123456";
String modifiedUser = "超级管理员";
Date modifiedTime = new Date();
Integer rows = userMapper.updatePasswordByUid(uid, password, modifiedUser, modifiedTime);
System.out.println("rows=" + rows);
}
@Test
public void findByUid() {
Integer uid = 7;
User result = userMapper.findByUid(uid);
System.out.println(result);
}
2 用户-修改密码-业务层
2.1 规划异常
1.用户在修改密码前,需要检查用户数据是否存在及是否被标记为“已删除”。如果检查不通过则应抛出UserNotFoundException异常。
2.用户修改密码时,可能会因为输入的原密码错误导致修改失败,则应抛出PasswordNotMatchException异常。
3.在执行修改密码时,如果返回的受影响行数与预期值不同,则应抛出UpdateException异常。
4.创建com.cy.store.service.ex.UpdateException异常类,继承自ServiceException类。
/** 更新数据的异常 */
public class UpdateException extends ServiceException {
// Override Methods...
}
2.2 接口与抽象方法
在IUserService中添加changePassword(Integer uid, String username, String oldPassword, String newPassword)抽象方法。
/**
* 修改密码
* @param uid 当前登录的用户id
* @param username 用户名
* @param oldPassword 原密码
* @param newPassword 新密码
*/
public void changePassword(Integer uid, String username, String oldPassword, String newPassword);
2.3 实现抽象方法
1.在UserServiceImpl类中实现changePassword()抽象方法。
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
// 检查查询结果是否为null
// 是:抛出UserNotFoundException异常
// 检查查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException异常
// 从查询结果中取出盐值
// 将参数oldPassword结合盐值加密,得到oldMd5Password
// 判断查询结果中的password与oldMd5Password是否不一致
// 是:抛出PasswordNotMatchException异常
// 将参数newPassword结合盐值加密,得到newMd5Password
// 创建当前时间对象
// 调用userMapper的updatePasswordByUid()更新密码,并获取返回值
// 判断以上返回的受影响行数是否不为1
// 是:抛了UpdateException异常
}
2.changePassword()方法的具体代码。
String中的equals与contentEquals方法,都可以用来比较String对象内容是否相同。
@Override
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
User result = userMapper.findByUid(uid);
// 检查查询结果是否为null
if (result == null) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 检查查询结果中的isDelete是否为1
if (result.getIsDelete().equals(1)) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 从查询结果中取出盐值
String salt = result.getSalt();
// 将参数oldPassword结合盐值加密,得到oldMd5Password
String oldMd5Password = getMd5Password(oldPassword, salt);
// 判断查询结果中的password与oldMd5Password是否不一致
if (!result.getPassword().contentEquals(oldMd5Password)) {
// 是:抛出PasswordNotMatchException异常
throw new PasswordNotMatchException("原密码错误");
}
// 将参数newPassword结合盐值加密,得到newMd5Password
String newMd5Password = getMd5Password(newPassword, salt);
// 创建当前时间对象
Date now = new Date();
// 调用userMapper的updatePasswordByUid()更新密码,并获取返回值
Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username, now);
// 判断以上返回的受影响行数是否不为1
if (rows != 1) {
// 是:抛出UpdateException异常
throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
}
}
3.在UserServiceTests中编写并执行单元测试。
@Test
public void changePassword() {
try {
Integer uid = 5;
String username = "lower";
String oldPassword = "123456";
String newPassword = "888888";
userService.changePassword(uid, username, oldPassword, newPassword);
System.out.println("密码修改成功!");
} catch (ServiceException e) {
System.out.println("密码修改失败!" + e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
3 用户-修改密码-控制器
3.1 处理异常
在用户修改密码的业务中抛出了新的UpdateException异常,需要在BaseController类中进行处理。
@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleException(Throwable e) {
JsonResult<Void> result = new JsonResult<Void>(e);
if (e instanceof UsernameDuplicateException) {
result.setState(4000);
} else if (e instanceof UserNotFoundException) {
result.setState(4001);
} else if (e instanceof PasswordNotMatchException) {
result.setState(4002);
} else if (e instanceof InsertException) {
result.setState(5000);
} else if (e instanceof UpdateException) {
result.setState(5001);
}
return result;
}
3.2 设计请求
设计用户提交的请求,并设计响应的方式。
请求路径:/users/change_password 请求参数:String oldPassword, String newPassword, HttpSession session 请求类型:POST 响应结果:JsonResult<Void>
3.3 处理请求
1.在UserController类中添加处理请求的changePassword(String oldPassword, String newPassword, HttpSession session)方法。
@RequestMapping("change_password")
public JsonResult<Void> changePassword(String oldPassword, String newPassword, HttpSession session) {
// 调用session.getAttribute("")获取uid和username
// 调用业务对象执行修改密码
// 返回成功
return null;
}
2.实现UserController控制器中的修改密码方法的代码。
@RequestMapping("change_password")
public JsonResult<Void> changePassword(String oldPassword, String newPassword, HttpSession session) {
// 调用session.getAttribute("")获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行修改密码
iUserService.changePassword(uid, username, oldPassword, newPassword);
// 返回成功
return new JsonResult<Void>(OK);
}
3.启动项目先登录,再访问http://localhost:8080/users/change_password?oldPassword=xx&newPassword=xx进行测试。
4 用户-修改密码-前端页面
1.在password.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。
<script type="text/javascript">
$("#btn-change-password").click(function() {
$.ajax({
url: "/users/change_password",
type: "POST",
data: $("#form-change-password").serialize(),
dataType: "json",
success: function(json) {
if (json.state == 200) {
alert("修改成功!");
} else {
alert("修改失败!" + json.message);
}
}
});
});
</script>
2.启动项目先登录,再访问http://localhost:8080/web/password.html页面并进行修改密码。
问题:如果无法正常将数据传递给后台,重启动系统和IDEA开发工具,登陆后便可修改密码。
3.问题:在操作前端页面时用户进入修改密码页面,长时间停留在当前页面未进行任何操作,将导致登录信息过期。此时点击修改按钮时,仍会向/users/change_password发送请求,会被拦截器重定向到登录页面。由于整个过程是由$.ajax()函数采用异步的方式处理的,所以重定向也是由异步任务完成的,在页面中没有任何表现就会出现“用户登录信息超时后点击按钮没有任何反应”的问题。
解决方案:可以在password.html页面的$.ajax()中补充error属性的配置,该属性的值是一个回调函数。当服务器未正常响应状态码时,例如出现302、400、404、405、500等状态码时,将会调用该函数。
error: function (xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
个人资料
1 用户-个人资料-持久层
1.1 规划需要执行的SQL语句
1.执行修改用户个人资料的SQL语句大致是:
UPDATE t_user SET phone=?, email=?, gender=?, modified_user=?, modified_time=? WHERE uid=?
2.在执行修改用户资料之前,当用户刚打开修改资料的页面时,就应把当前登录的用户信息显示到页面中。显示用户资料可以通过:
SELECT * FROM t_user WHERE uid=?
说明:
1.该查询功能已经实现,无需再次开发;
2.在执行修改用户资料之前,还应检查用户数据是否存在、是否标记为“已删除”,也可以通过以上查询来实现。
1.2 接口与抽象方法
在UserMapper接口中添加updateInfoByUid(User user)方法。
/**
* 根据uid更新用户资料
* @param user 封装了用户id和新个人资料的对象
* @return 受影响的行数
*/
Integer updateInfoByUid(User user);
1.3 配置SQL映射
1.在UserMapper.xml中配置Integer updateInfoByUid(User user)抽象方法的映射。
<!-- 根据uid更新用户个人资料:Integer updateInfoByUid(User user) -->
<update id="updateInfoByUid">
UPDATE
t_user
SET
<if test="phone != null">phone = #{phone},</if>
<if test="email != null">email = #{email},</if>
<if test="gender != null">gender = #{gender},</if>
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
WHERE
uid = #{uid}
</update>
2.在UserMapperTests中编写并执行单元测试。
@Test
public void updateInfoByUid() {
User user = new User();
user.setUid(20);
user.setPhone("17858802222");
user.setEmail("admin@cy.com");
user.setGender(1);
user.setModifiedUser("系统管理员");
user.setModifiedTime(new Date());
Integer rows = userMapper.updateInfoByUid(user);
System.out.println("rows=" + rows);
}
2 用户-个人资料-业务层
2.1 规划异常
1.关于用户修改个人资料是由两个功能组成的。
-
打开页面时显示当前登录的用户的信息;
-
点击修改按钮时更新用户的信息。
2.关于打开页面时显示当前登录的用户的信息,可能会因为用户数据不存在、用户被标记为“已删除”而无法正确的显示页面,则抛出UserNotFoundException异常。
3.关于点击修改按钮时更新用户的信息,在执行修改资料之前仍需再次检查用户数据是否存在、用户是否被标记为“已删除”,则可能抛出UserNotFoundException异常。并且在执行修改资料过程中,还可能抛出UpdateException异常。
2.2 接口与抽象方法
在IUserService接口中添加两个抽象方法,分别对应以上两个功能。
/**
* 获取当前登录的用户的信息
* @param uid 当前登录的用户的id
* @return 当前登录的用户的信息
*/
User getByUid(Integer uid);
/**
* 修改用户资料
* @param uid 当前登录的用户的id
* @param username 当前登录的用户名
* @param user 用户的新的数据
*/
void changeInfo(Integer uid, String username, User user);
2.3 实现抽象方法
1.在UserServiceImpl实现类中实现getByUid(Integer uid)和changeInfo(Integer uid, String username, User user)以上两个抽象方法。
@Override
public User getByUid(Integer uid) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
// 判断查询结果是否为null
// 是:抛出UserNotFoundException异常
// 判断查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException异常
// 创建新的User对象
// 将以上查询结果中的username/phone/email/gender封装到新User对象中
// 返回新的User对象
return null;
}
@Override
public void changeInfo(Integer uid, String username, User user) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
// 判断查询结果是否为null
// 是:抛出UserNotFoundException异常
// 判断查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException异常
// 向参数user中补全数据:uid
// 向参数user中补全数据:modifiedUser(username)
// 向参数user中补全数据:modifiedTime(new Date())
// 调用userMapper的updateInfoByUid(User user)方法执行修改,并获取返回值
// 判断以上返回的受影响行数是否不为1
// 是:抛出UpdateException异常
}
2.getByUid(Integer uid)和changeInfo(Integer uid, String username, User user)方法的具体代码实现。
@Override
public User getByUid(Integer uid) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
User result = userMapper.findByUid(uid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 判断查询结果中的isDelete是否为1
if (result.getIsDelete().equals(1)) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 创建新的User对象
User user = new User();
// 将以上查询结果中的username/phone/email/gender封装到新User对象中
user.setUsername(result.getUsername());
user.setPhone(result.getPhone());
user.setEmail(result.getEmail());
user.setGender(result.getGender());
// 返回新的User对象
return user;
}
@Override
public void changeInfo(Integer uid, String username, User user) {
// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
User result = userMapper.findByUid(uid);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 判断查询结果中的isDelete是否为1
if (result.getIsDelete().equals(1)) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在");
}
// 向参数user中补全数据:uid
user.setUid(uid);
// 向参数user中补全数据:modifiedUser(username)
user.setModifiedUser(username);
// 向参数user中补全数据:modifiedTime(new Date())
user.setModifiedTime(new Date());
// 调用userMapper的updateInfoByUid(User user)方法执行修改,并获取返回值
Integer rows = userMapper.updateInfoByUid(user);
// 判断以上返回的受影响行数是否不为1
if (rows != 1) {
// 是:抛出UpdateException异常
throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
}
}
3.在UserServiceTests类中进行单元测试。
@Test
public void getByUid() {
try {
Integer uid = 20;
User user = iUserService.getByUid(uid);
System.out.println(user);
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
@Test
public void changeInfo() {
try {
Integer uid = 20;
String username = "数据管理员";
User user = new User();
user.setPhone("15512328888");
user.setEmail("admin03@cy.cn");
user.setGender(2);
iUserService.changeInfo(uid, username, user);
System.out.println("OK.");
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
3 用户-个人资料-控制器
3.1 处理异常
说明:无需再次开发。
3.2 设计请求
1.设计用户提交显示当前登录的用户信息的请求,并设计响应的方式。
请求路径:/users/get_by_uid 请求参数:HttpSession session 请求类型:GET 响应结果:JsonResult<User>
2.设计用户提交执行修改用户信息的请求,并设计响应的方式。
请求路径:/users/change_info 请求参数:User user, HttpSession session 请求类型:POST 响应结果:JsonResult<Void>
3.3 处理请求
1.处理获取用户信息请求
1.在UserController类中添加处理请求的getByUid()方法,并导入org.springframework.web.bind.annotation.GetMapping包。
@GetMapping("get_by_uid")
public JsonResult<User> getByUid(HttpSession session) {
// 从HttpSession对象中获取uid
// 调用业务对象执行获取数据
// 响应成功和数据
return null;
}
2.getByUid(HttpSession session)方法中具体代码实现为。
@GetMapping("get_by_uid")
public JsonResult<User> getByUid(HttpSession session) {
// 从HttpSession对象中获取uid
Integer uid = getUidFromSession(session);
// 调用业务对象执行获取数据
User data = userService.getByUid(uid);
// 响应成功和数据
return new JsonResult<User>(OK, data);
}
3.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/users/get_by_uid请求进行测试。
2.处理修改用户个人信息请求
1.在UserController类中添加处理请求的changeInfo(User user, HttpSession session)方法。
@RequestMapping("change_info")
public JsonResult<Void> changeInfo(User user, HttpSession session) {
// 从HttpSession对象中获取uid和username
// 调用业务对象执行修改用户资料
// 响应成功
return null;
}
2.changeInfo(User user, HttpSession session)方法中具体代码实现为。
@RequestMapping("change_info")
public JsonResult<Void> changeInfo(User user, HttpSession session) {
// 从HttpSession对象中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
// 调用业务对象执行修改用户资料
userService.changeInfo(uid, username, user);
// 响应成功
return new JsonResult<Void>(OK);
}
3.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/users/change_info?phone=17858800000&email=admin07@cy.com&gender=1进行测试。
4 用户-个人资料-前端页面
1.在userdata.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。
<script type="text/javascript">
$(document).ready(function() {
$.ajax({
url: "/users/get_by_uid",
type: "GET",
dataType: "json",
success: function(json) {
if (json.state == 200) {
console.log("username=" + json.data.username);
console.log("phone=" + json.data.phone);
console.log("email=" + json.data.email);
console.log("gender=" + json.data.gender);
$("#username").val(json.data.username);
$("#phone").val(json.data.phone);
$("#email").val(json.data.email);
let radio = json.data.gender == 0 ? $("#gender-female") : $("#gender-male");
radio.prop("checked", "checked");
} else {
alert("获取用户信息失败!" + json.message);
}
}
});
});
$("#btn-change-info").click(function() {
$.ajax({
url: "/users/change_info",
type: "POST",
data: $("#form-change-info").serialize(),
dataType: "json",
success: function(json) {
if (json.state == 200) {
alert("修改成功!");
location.href = "login.html";
} else {
alert("修改失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
});
</script>
2.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/web/userdata.html页面并进行用户个人资料的修改测试。