1. 修改密码
需要用户提交原始密码和新密码,再根据当前登录的用户进行信息的修改操作。
持久层
- 规划需要执行的SQL语句
根据用户的uid修改用户的password值。
根据uid查询用户的数据。在修改密码之前,首先要保证当前这个用户的数据存在,还要检测是否被标记为已经删除、检测输入的原始密码是否正确。update t_user set password = ?, modified_user = ?, modified_time = ? where uid = ?;
select * from t_user where uid = ?;
- 设计接口和抽象方法
UserMapper接口,将以上的两个方法的抽象定义出来。然后映射到SQL语句上。/** * 根据用户的uid来修改用户密码 * @param uid 用户的id * @param password 用户输入的新密码 * @param modifiedUser 表示修改的执行者 * @param modifiedTime 表示修改数据的时间 * @return 返回值为受影响的行数 */ Integer updatePasswordByUid(Integer uid, String password, String modifiedUser, Date modifiedTime); //根据用户id查询用户数据 User fndByUid(Integer uid);
- SQL的映射
配置到映射文件UserMapper.xml中
做单元测试。<update id="updatePasswordByUid"> update t_user set password = #{password}, modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where uid = #{uid} </update> <select id="fndByUid" resultMap="UserEntityMap"> select * from t_user where uid = #{uid} </select>
@Test public void updatePasswordByUid() { userMapper.updatePasswordByUid(9,"543","管理员",new Date()); } @Test public void fndByUid() { System.out.println(userMapper.fndByUid(9)); }
业务层
- 异常规划
1.用户的原始密码错误、is_delete=1、uid找不到,属于用户没有发现异常。
2.update在更新的时候,有可能产生未知的异常,UpdateException。 - 设计接口和抽象方法
执行用户修改密码的核心方法。
在实现类中实现当前的抽象方法。void changePassword(Integer uid, String username, String oldPassword, String newPassword);
单元测试@Override public void changePassword(Integer uid, String username, String oldPassword, String newPassword) { User result = userMapper.fndByUid(uid); if(result == null || result.getIsDelete() == 1) { throw new UserNotFoundException("用户数据不存在"); } //原始密码和数据库中密码进行比较 String oldMd5Password = getMD5Password(oldPassword,result.getSalt()); //对输入的密码加密再和数据库中的密码比较 if(!result.getPassword().equals(oldMd5Password)) { throw new PasswordNotMatchException("密码错误"); } //将新的密码设置到数据库中,将新的密码进行加密再去更新 String newMd5Password = getMD5Password(newPassword, result.getSalt()); Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username,new Date()); if(rows != 1) { throw new UpdateException("更新数据产生未知的异常"); } }
@Test public void changePassword() { userService.changePassword(10,"管理员","123","321"); }
控制层
- 异常处理
UpdateException需要配置在统一的异常处理方法中。else if (e instanceof UpdateException) { result.setState(5003); result.setMessage("更新数据时产生未知的异常"); }
- 设计请求
/users/change_password post String oldPassword, String newPassword, HttpSession session //需要和表单中的name属性值保持一致 JsonResult<Void>
- 处理请求
@RequestMapping("change_password") public JsonResult<Void> changePassword(String oldPassword, String newPassword, HttpSession session) { Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); userService.changePassword(uid, username,oldPassword,newPassword); return new JsonResult<>(OK); }
前端页面
在password.html中,添加ajax请求的处理,不再手动去编写ajax结构,直接复制原来的,然后修改参数。
<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("密码修改失败");
}
},
error: function (xhr) {
alert("修改密码时产生未知的异常" + xhr.message);
}
});
});
</script>
2. 修改个人资料
持久层
- 需要规划SQL语句
1.更新用户信息的SQL语句
2.根据用户名查询用户的数据update t_user set phone = ?,email = ?,gender = ?,modified_user = ?,modified_time = ? where uid = ?;
select * from t_user where uid = ?;
- 接口与抽象方法
更新用户的信息方法的定义//更新用户的信息 Integer updateInfoByUid(User user);
- 抽象方法的映射
在UserMapper.xml文件中进行映射编写
在测试类中测试<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>
@Test public void updateInfoByUid() { User user = new User(); user.setUid(9); user.setPhone("1883424234"); user.setEmail("test001@qq.com"); user.setGender(1); userMapper.updateInfoByUid(user); }
业务层
-
异常规划
1.设计两个功能
- 当打开页面时,获取用户的信息并且填充到对应的文本框中(数据回显)。
- 检测用户是否点击了修改按钮,如果检测到则执行修改用户信息的操作(修改信息)。
2.打开页面的时候可能找不到用户的数据,点击修改按钮之前需要再次的去检测用户的数据是否存在。
-
接口和抽象方法
主要有两个功能的模块,对应的是两个抽象的方法的设计。//根据用户的id查询用户的数据 User getByUid(Integer uid); //更新用户的数据操作 void changeInfo(Integer uid, String username, User user);
-
实现抽象方法
在UserServiceImpl类中添加两个抽象方法的具体实现。@Override public User getByUid(Integer uid) { User result = userMapper.fndByUid(uid); if(result==null || result.getIsDelete()==1) { throw new UserNotFoundException("用户数据不存在"); } User user = new User(); user.setUsername(result.getUsername()); user.setPhone(result.getPhone()); user.setEmail(result.getEmail()); user.setGender(result.getGender()); return user; } @Override public void changeInfo(Integer uid, String username, User user) { User result = userMapper.fndByUid(uid); if(result==null || result.getIsDelete()==1) { throw new UserNotFoundException("用户数据不存在"); } user.setUid(uid); user.setUsername(username); user.setModifiedUser(username); user.setModifiedTime(new Date()); Integer rows = userMapper.updateInfoByUid(user); if(rows != 1) { throw new UpdateException("更新数据产生的异常"); } }
在测试类中进行测试
@Test public void getByUid() { System.out.println(userService.getByUid(9)); } @Test public void changeInfo() { User user = new User(); user.setPhone("13932243424"); user.setEmail("292791@qq.com"); user.setGender(0); userService.changeInfo(9,"管理员",user); }
控制层
- 异常规划
无需重复开发 - 设计请求
1.设置一打开页面就发送当前用户数据的查询
2.点击修改按钮发送用户的数据修改操作请求的设计/users/get_by_uid GET HttpSession session JsonResult<User>
/users/change_info POST User user, HttpSession session JsonResult<Void>
- 处理请求
@RequestMapping("get_by_uid") public JsonResult<User> getByUid(HttpSession session) { User data = userService.getByUid(getuidFromSession(session)); return new JsonResult<>(OK, data); } @RequestMapping("change_info") public JsonResult<Void> changeInfo(User user, HttpSession session) { //user对象有四部分的数据:username、phone、email、gender //uid也要封装到user对象中 Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); userService.changeInfo(uid,username,user); return new JsonResult<>(OK); }
前端页面
1.在打开userdata.html页面自动发送ajax请求(get_by_uid),查询到的数据填充到这个页面。
<script type="text/javascript">
/**
* $(document).ready(function() {
* //业务代码
* });
*/
$(document).ready(function() {
$.ajax({
url: "/users/get_by_uid",
type: "get",
data: $("#form-change-info").serialize(),
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
$("#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");
//prop()表示给某个元素添加属性及属性的值
radio.prop("checked","checked");
} else {
alert("用户的数据不存在");
}
},
error: function (xhr) {
alert("查询用户信息时产生未知的异常" + xhr.message);
}
});
});
</script>
2.在检测到用户点击了修改按钮之后,发送一个ajax请求(change_info)。
$("#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 = "userdata.html";
} else {
alert("用户信息修改失败");
}
},
error: function (xhr) {
alert("用户信息修改时产生未知的异常" + xhr.message);
}
});
});
3. 上传头像
持久层
- SQL语句的规划
数据库中,保存头像的字段类型是varchar:将对应文件保存在操作系统上,然后再把这个文件路径给记录下来,因为在记录路径的时候是非常便捷和方便,将来如果要打开这个文件可以依据这个路径去找到这个文件。所以,在数据库中需要保存这个文件的路径即可。
对应是一个更新用户avatar字段的sql语句update t_user set avatar = ?, modified_user = ?, modified_time = ? where uid = ?;
- 设计接口和抽象方法
UserMapper接口中定义一个抽象方法用于修改用户的头像。//根据用户uid值来修改用户的头像 /* @Param("Sql映射文件中#{}占位符的变量名") 解决的问题:当SQL语句的占位符和映射的接口方法参数名不一致时,需要将某个参数强行注入到某个占位符变量上时 可以用@Param这个注解来标注映射关系 */ Integer updateAvatarByUid( @Param("uid") Integer uid, @Param("avatar") String avatar, @Param("modifiedUser") String modifiedUser, @Param("modifiedTime") Date modifiedTime);
- 接口的映射
UserMapper.xml文件中编写映射的SQL语句。<update id="updateAvatarByUid"> update t_user set avatar = #{avatar}, modified_user = #{modifiedUser}, modified_time = #{modifiedTime} where uid = #{uid} </update>
- 在测试类测试
@Test public void updateAvatarByUid() { userMapper.updateAvatarByUid(9,"/upload/avatar.png","管理员",new Date()); }
业务层
-
规划异常
1.用户数据不存在,找不到对应的用户数据
2.更新的时候,各种未知异常产生无需重复开发
-
设计接口和抽象方法
//修改用户的头像 void changeAvatar(Integer uid, String avatar, String username);
-
实现抽象方法
@Override public void changeAvatar(Integer uid, String avatar, String username) { User result = userMapper.fndByUid(uid); if(result == null || result.getIsDelete().equals(1)) { throw new UserNotFoundException("用户数据不存在"); } Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date()); if(rows != 1) { throw new UpdateException("更新用户头像产生未知异常"); } }
-
在测试类中测试
@Test public void changeAvatar() { userService.changeAvatar(9,"/upload/test.png","小明"); }
控制层
- 异常规划
文件异常的父类: FileUploadException:泛指文件上传的异常(父类)继承RuntimeException 子类: FileEmptyException:文件为空的异常 FileSizeException:文件大小超出限制 FileStateException:文件状态异常(比如已经打开) FileTypeException:文件类型异常 FileUploadIOException:文件读写的异常 五个构造方法显示的声明出来,去继承父类
- 处理异常
在基类BaseController类中统一编写。
然后在异常统一处理方法的参数列表上增加新的异常处理作为它的参数。else if (e instanceof FileEmptyException) { result.setState(6000); } else if (e instanceof FileSizeException) { result.setState(6001); } else if (e instanceof FileTypeException) { result.setState(6002); } else if (e instanceof FileStateException) { result.setState(6003); } else if (e instanceof FileUploadIOException) { result.setState(6004); }
@ExceptionHandler({ServiceException.class, FileUploadException.class})
- 设计请求
/users/change_avatar post (get请求提交数据2KB) HttpSession session, MutipartFile file JsonResult<String>
- 实现请求
//设置上传文件的最大值 public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024; //限制上传文件的类型 public static final List<String> AVATAR_TYPE = new ArrayList<>(); static { AVATAR_TYPE.add("images/jpeg"); AVATAR_TYPE.add("images/png"); AVATAR_TYPE.add("images/bmp"); AVATAR_TYPE.add("images/gif"); } /* MultipartFile接口是SpringMVC提供的一个接口,这个接口为我们包装了获取文件类型的数据(任何类型的file都可以接收) SpringBoot它又整合了SpringMVC,只需要在处理请求的方法参数列表上声明一个参数类型为MultipartFile的参数, 然后SpringBoot自动将传递给服务器的文件数据赋值给这个参数。 @RequestParam:表示请求中的参数,将请求中的参数注入请求处理方法的某个参数上, 如果名称不一致则可以使用@RequestParam注解进行标记和映射 */ @RequestMapping("change_avatar") public JsonResult<String> changeAvatar(HttpSession session, @RequestParam("file") MultipartFile file) { if(file.isEmpty()) { throw new FileEmptyException("文件为空"); } if(file.getSize() > AVATAR_MAX_SIZE) { throw new FileSizeException("文件超出限制大小"); } //判断文件的类型是否是我们规定的后缀类型 String contentType = file.getContentType(); if(!AVATAR_TYPE.contains(contentType)) { throw new FileTypeException("文件类型不支持"); } String parent = session.getServletContext().getRealPath("upload"); //File对象指向这个路径,File是否存在 File dir = new File(parent); if(!dir.exists()) { //检测目录是否存在 dir.mkdirs(); //创建当前目录 } //获取到这个文件名称,UUID工具类生成一个新的字符串作为文件名 String originalFilename = file.getOriginalFilename(); System.out.println("OriginalFilename:" + originalFilename); int index = originalFilename.lastIndexOf("."); String suffix = originalFilename.substring(index); String filename = UUID.randomUUID().toString().toUpperCase() + suffix; File dest = new File(dir, filename); //空文件 //参数file中数据写入到这个空文件汇总 try { file.transferTo(dest); } catch (IOException e) { throw new FileUploadIOException("文件读写异常"); }catch (FileStateException e) { throw new FileStateException("文件状态异常"); } Integer uid = getuidFromSession(session); String username = getUsernameFromSession(session); //返回头像的路径 /upload/test.png String avatar = "/upload" + filename; userService.changeAvatar(uid, avatar,username); //返回用户头像的路径给前端页面,将来用于头像展示使用 return new JsonResult<>(OK,avatar); }
前端页面
在upload页面中编写上传头像的代码。
<form
action="/users/change_avatar"
method="post"
enctype="multipart/form-data"
说明:如果直接使用表单进行文件的上传,需要给表单显示的添加一个属性enctype=“multipart/form-data”,表示不会将目标文件的数据结构做修改再上传。
页面优化
1. 上传的图片超出1MB:更改默认的大小限制
SpringMVC默认1MB大小的文件可以进行上传,手动的去修改SpringMVC默认上传文件的大小。
直接在配置文件中进行配置:
spring.servlet.multipart.maxFileSize=10MB
spring.servlet.multipart.maxRequestSize=15MB
2. 显示头像
在页面中通过ajax请求来提交文件,提交完成后返回了json串,解析出data中数据,设置到img头像标签的src属性上。
- serialize():可以将表单数据自动拼接成key=value的结构进行提交给服务器,一般提交是普通的控件类型中的数据(text/password/radio/checkbox)等。
- FormData类:将表单中数据保持原有的结构进行数据的提交。
new FormData($("#form")[0]);
- ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。关闭这两个默认的功能。
processData: false, //处理数据的形式,关闭处理数据 contentType: false, //提交数据的形式,关闭默认提交数据的形式
<script type="text/javascript">
$("#btn-change-avatar").click(function () {
$.ajax({
url: "/users/change_avatar",
type: "post",
data: new FormData($("#form-change-avatar")[0]),
processData: false, //处理数据的形式,关闭处理数据
contentType: false, //提交数据的形式,关闭默认提交数据的形式
dataType: "JSON",
success: function (json) {
if(json.state == 200) {
alert("头像修改成功");
//将服务器端返回的头像地址设置img标签的src属性上
//attr(属性,属性的值)
$("#img-avatar").attr("src", json.data);
} else {
alert("头像修改失败");
}
},
error: function (xhr) {
alert("修改头像时产生未知的异常" + xhr.message);
}
});
});
</script>
3. 登录后显示头像
可以更新头像成功后,将服务器返回的头像路径保存在客户端cookie对象,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动检测去读取cookie中头像并设置到src属性上。
- 引入cookie.js文件
在login.html中调用cookie方法<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
if(json.state == 200) { //alert("登录成功"); location.href = "index.html"; //将服务器返回的头像设置到cookie中 $.cookie("avatar",json.data.avatar,{expires: 7}); }
- 在upload.html页面先引入cookie.js文件
- 在upload.html页面通过ready()自动读取cookie中的数据。
$(document).ready(function () { let avatar = $.cookie("avatar"); //将cookie值获取出来设置到头像的src属性上 $("#img-avatar").attr("src", avatar); });
4. 显示最新的头像
在更改完头像后,将最新的头像地址,再次保存到cookie,同名保存会覆盖cookie中的值。
//将头像保存在cookie中
$.cookie("avatar",json.data,{expires: 7});