系统各个功能实现博客:
文章目录
- 系列文章目录
目录
修改密码
分析:需要提交原始密码和新密码,根据用户登录信息进行修改操作。
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)抽象方法。
//根据用户id修改密码
Integer updatePassworByUid(@Param("uid") Integer uid,
@Param("password") String password,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
//根据Uid查询用户数据
User findUserByUid(@Param("uid") Integer uid);
1.3 在映射文件中绑定配置
<update id="findUserByUid">
update t_user set
password=#{password},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where uid = #{uid}
</update>
<delete id="deleteUserById" parameterType="int">
delete from t_user where uid = #{uid}
</delete>
1.4 单元测试
在UserMapperTests中编写并执行单元测试。
@Test
public void findUserByUid(){
User result = userMapper.findUserByUid(5);
System.out.println(result);
}
@Test
public void updatePassworByUid(){
Integer result = userMapper.updatePassworByUid(5,"123455","管理员",new Date());
System.out.println(result);
}
由于我们update操作跨过了业务层,顾我们测试时更新的密码时未经过加密的,但是后续通过前端调用业务层时是会完善的
2.修改密码-业务层
2.1 规划异常
1.用户原密码错误、is_delete == 1、uid找不到。
2. 新异常,更新操作时系统 异常。类配置方法照旧。创建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)抽象方法。参数包括:修改密码的用户uid,旧密码,新密码,用户名。
Integer changePassword(Integer uid, String username, String oldPassword, String newPassword);
2.3 实现抽象方法
在UserServiceImpl中实现。
@Override
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
User result = userMapper.findUserByUid(uid);
if (result == null){
throw new UserNotFoundException("用户数据不存在!");
}
String salt = result.getSalt();
oldPassword = getMd5Password(oldPassword,salt);
if (oldPassword != result.getPassword()) {
throw new PasswordNotMatchException("用户名密码错误!");
}
String newMd5Password = getMd5Password(newPassword,salt);
Integer integer = userMapper.updatePassworByUid(uid, newMd5Password, username, new Date());
if (integer!=1) {
throw new UpdateException("修改密码失败!");
}
}
2.4 单元测试
@Test
public void changePassword(){
userService.changePassword(7,"chenxi","123","123456");
}
3.修改密码-控制层
3.1 处理异常
在用户修改密码的业务中抛出了新的UpdateException异常,需要在BaseController类中进行处理。
else if (e instanceof UpdateException){
result.setState(5003);
result.setMessage("用户密码异常!");
}
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("/users/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,"成功!",null);
}
4.修改密码-前端页面
经过前几次的熟悉,我们这个可以直接进行复制然后微调参数
<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("修改密码成功!");
location.href = "index.html"
} else {
alert("修改密码失败!");
}
},
error: function (xhr) {
alert("修改密码时产生的位置异常:" + xhr.message);
}
})
})
</script>
修改个人资料
1.个人资料-持久层
1.1 规划SQL语句
修改个人资料的语句:
update t_user set phone = ?, email = ?,gender = ?,modified_user = ?, modified_time = ? where uid = ?
根据用户id来查询用户的数据
select * from t_user where uid = ?
1.2接口与抽象方法
Integer updateInfoByUid(User user);
1.3 抽象方法的映射
if表示条件判断标签,test接收的是一个返回值为boolean类型的条件,如果test条件的结果为true则执行if标签内部的语句
<update id="updateInfoByUid" parameterType="user">
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>
1.4 单元测试
在UserMapper.xml中创建配置方法
@Test
public void updateInfoByUid(){
User user = new User();
user.setUid(7);
user.setPhone("110");
user.setEmail("785822438@ppcom");
user.setGender(0);
Integer result = userMapper.updateInfoByUid(user);
System.out.println(result);
}
2.个人资料-业务层
2.1 异常规划
可能找不到用户的数据
2.2 接口与抽象方法
- 点击进入修改资料页面时,获取用户本来的信息,并且填充到对应文本框中。
- 修改按钮的点击方法,点击即执行修改操作
两大功能模块,查询、修改。
User getUserById(Integer uid);
Integer changeInfo(Integer uid,String username,User user);
2.3 抽象方法实现
在UserServiceImpl中编写两个方法。
@Override
public User getUserById(Integer uid) {
User result = userMapper.findUserByUid(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 result;
}
@Override
public Integer changeInfo(Integer uid, String username, User user) {
User result = userMapper.findUserByUid(uid);
//判断是否存在或者被逻辑删除
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户未找到!");
}
//前端传来的user没有uid和username
user.setUsername(username);
user.setUid(uid);
user.setModifiedUser(username);
user.setModifiedTime(new Date());
Integer row = userMapper.updateInfoByUid(user);
if (row!=1) {
throw new UpdateException("更新数据失败!");
}
return row;
}
2.4 单元测试
@Test
public void getUserById(){
User userById = userService.getUserById(7);
System.out.println(userById);
}
@Test
public void changeInfo(){
User user = new User();
user.setPhone("123123");
user.setEmail("12421341");
user.setGender(1);
userService.changeInfo(7,"管理员",user);
}
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.在UserController类中添加处理请求的getByUid()方法
@RequestMapping("/users/get_by_uid")
public JsonResult<User> getInfoByUid(HttpSession session){
// 从HttpSession对象中获取uid
Integer uid = getUidFromSession(session);
// 调用业务对象执行获取数据
User result = userService.getUserById(uid);
// 响应成功和数据
return new JsonResult<User>(OK,"获取成功!",result);
}
2.处理修改用户个人信息请求,.在UserController类中添加处理请求的changeInfo(User user, HttpSession session)方法。
@RequestMapping("/users/change_info")
public JsonResult<Void> changeInfo(User user,HttpSession session){
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
userService.changeInfo(uid, username, user);
return new JsonResult<>(OK,"修改成功!",null);
}
3.完成后启动项目,打开浏览器先登录,再访问http://localhost:8080/users/change_info?phone=178 58800000&email=admin07@cy.com&gender=1进行测试。
4.个人资料-前端页面
1.在打开userdata.html页面自动发送ajax请求,查询到的数据填充到这个页面。
$(document).ready(function () {
$.ajax({
url: "/users/get_by_uid",
type: "POST",
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
$("#username").val(json.data.username)
$("#email").val(json.data.email)
$("#phone").val(json.data.phone)
}
}
})
})
⒉.在检测到用户点击了修改按钮之后发送一个ajax请求。
$("#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("修改成功!");
} else {
alert("修改失败!"+json.message);
}
},
error: function (xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html"
}
})
})
个人头像上传 (重难点)
1.头像上传-持久层
1. SQL语句规划
用户头像图片的保存如果采用解析成字节流保存在数据库中的话效率太低,且不方便实现,我们可以将图片保存在本地或者指定服务器上,将图片路径记录下来保存进数据库,之后可根据路径去查找图片会十分便捷。部分公司便是将一些静态资源保存在某电脑上将其作为单独的服务器使用。
所以我们本质时一个更新语句,获取了图片路径之后对用户表下的avatar字段进行更新。
update t_user set avatar = ? , modified_user = ?, modified_time = ? where uid = ?
1.2 设计接口和抽象方法
1.在UserMapper中声明方法。
//根据Uid更新用户头像数据
Integer updateAvatarByUid(@Param("uid") Integer uid,
@Param("avatar") String avatar,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
2.在UserMapper.xml中实现
<update id="updateAvatarByUid">
update t_user set
avatar=#{avatar},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where uid = #{uid}
</update>
1.3.单元测试
@Test
public void updateAvatarByUid(){
Integer row = userMapper.updateAvatarByUid(5, "/chenxi/shuai", "管理员", new Date());
System.out.println(row);
}
2.头像上传-业务层
2.1 异常规划
1.用户数据不存在,找不到对应的用户的数据。
2.更新的时候,各位未知异常产生。
都无需重复开发
2.2设计接口和抽象方法
/**
*修改用户的头像
* @param uid 用户的id
* @param avatar 用户头像的路径
* @param username 用户的名称
*/
Integer changeAvatarByUid(Integer uid, String avatar, String username);
2.3 实现抽象方法
1.在UserServiceImpl重写changeAvatarByUid方法。
@Override
public Integer changeAvatarByUid(Integer uid, String avatar, String username) {
//首先查询用户数据是否存在
User result = userMapper.findUserByUid(uid);
//判断是否存在或者被逻辑删除
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户未找到!");
}
Integer row = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
if (row!=1) {
throw new UpdateException("更新数据失败!");
}
return row;
}
2.4 单元测试
@Test
public void changeAvatar(){
Integer row = userService.changeAvatarByUid(5, "/chenxi/niubi", "管理员");
System.out.println(row);
}
3.头像上传-控制层
- 在服务器中需要有专门的目录来存放上传路径 ,在新的上传请求传来时,必应判断该上传路径是否存在,若不存在 ,则需要先创建该上传目录。
- 在服务器中的文件,为了防止上传文件的名称重复(防止重复提交同一文件),从而产生冲突等问题,应用 UUID 来生成随机 ID 为新文件命名
- 文件上传应当将上传至服务器的url地址保存在数据库中
3.1 规划异常
文件异常的父类:
FileUploadException泛指文件上传的异常(父类)继承RuntimeException
父类是:FileUploadException
FileEmptyException 文件为空的异常FileSizeException 文件大小超出限制
FileTypeException 文件类型异常
FileStateException 文件状态异常:上传时文件状态有异常。
FileUploadIOException 文件读写的异常
3.2 实现异常类
因为这是在前端页面数据传递申请控制层时便会出现的异常,我们在Controller层下新建一个ex包存放我们的文件异常。
然后再我们的BaseController下添加我们的异常。文件的异常状态码我统一从6000开始配置。
else if (e instanceof FileSizeException){
result.setState(6001);
result.setMessage("文件大小异常!");
}else if (e instanceof FileStateException){
result.setState(6002);
result.setMessage("文件状态异常!");
}else if (e instanceof FileTypeException){
result.setState(6003);
result.setMessage("文件类型不匹配!");
}else if (e instanceof FileUploadIOException){
result.setState(6004);
result.setMessage("文件上传异常!");
}else if (e instanceof FileEmptyException){
result.setState(6000);
result.setMessage("上传文件为空!");
然后我们要再@ExceptionHandler(ServiceException.class)中添加文件异常,怎么添加呢,我们看看源码,可以看到它的参数时可以以一个列表来注册的,但是类型必须时class类型
//当前项目中产生的ServiceException、FileUploadException异常会被统一的拦截到此方法中
@ExceptionHandler({ServiceException.class,FileUploadException.class})//用于处理统一抛出的异常
3.3 设计请求
MultipartFile接口:这是由SpringMVC提供的一个接口,这个接口为我们包装了获取文件类型的数据(任何类型都可以接受),SpringBoot整合了SpringMVC,所以我们只需要在处理请求的方法参数列表上申明MultipartFile类型的参数,SpringBoot自动将传递给服务的文件数据赋值赋值给这个参数
@RequestParam表示请求中的参数,将请求中的参数注入请求处理方法的某个参数上,如果名称不一致则可以使用@RequestParam注解进行标记和映射。
请求路径:/users/change_avatar
请求参数:HttpSession session, MultipartFile file
请求类型:POST(get请求提交数据一般不超过2kb,顾选择post)
响应结果:JsonResult <String>(返回图片的路径,再用户点进时便加载。)
3.4 实现请求
@RequestMapping("/users/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 type = file.getContentType();
if (!AVATAR_TYPE.contains(type)) {
throw new FileTypeException("文件类型错误!");
}
//
String parent = session.getServletContext().getRealPath("/upload");
System.out.println(parent);
File dir = new File(parent);
if (!dir.exists()) { //检测目录是否存在
dir.mkdirs(); //不存在则创建目录
}
//为防止用户上传相同名字的图片导致被覆盖,我们使用uuid工具类来生成一个文件名来作为新名字
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename" + originalFilename);
//获取文件结尾,例如获取avatar.png的png
int index = originalFilename.lastIndexOf(".");
String suffix = originalFilename.substring(index);
//生成UUID,拼接成新的文件名
String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
//在dir目录下保存图片,将file写入dest中,通过transferTo直接写入。
File dest = new File(dir, filename);
try {
file.transferTo(dest);
}catch (IOException e) {
throw new com.chenxi.stores.controller.ex.FileUploadIOException("文件写入异常!");
}
//调用并存入avatar
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
String avatar = "/upload/" + filename;
userService.changeAvatarByUid(uid,avatar,username);
//返回用户头像目录给前端。
return new JsonResult<String>(OK,"修改成功!",avatar);
}
3.5 单元测试
图片上传无法进行单元测试
4.头像上传-前端页面
文件上传可以不用ajax请求,可以直接用表单提交将文件发送给后端。如果想直接使用表单进行文件上传,需要添加属性enctype="multipart/form-data",不会将目标文件的数据结构做修改在上传。并且建议使用post提交。
但是无法显示头像,顾不贴代码,显示头像看5.2.
5. 解决BUG
5.1更改默认的大小限制
SpringMVC默认为1MB文件可以进行上传,手动的去修改SpringMVC默认上传文件的大小。
5.2 修改后显示头像
在页面中通过ajax请求来提交文件,提交完成后返回了json串,解析出data中数据,设置到img头像标签的src属性上就可以了。给form添加id="form-change-avatar"
由于submit无法添加点击事件,顾改为button
编写Ajax请求
注意我们处理表单的数据也要变化:
- serialize():可以将表单数据数据自动拼接成key=value的结构进行提交给服务器,一般提交是普通的控件类型中的数据(textlpassword\radiolcheckbox)等等
- FormData类:将表单中数据保持原有的结构进行数据的条件。new FormData($("#form)"[o]),[0]表示表单中的第一个元素。
- 由于ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。所以我们要关闭其功能。
processData: false,//处理数据的形式,关闭处理数据
contentType: false,//提交数据的形式,关闭默认提交数据的形式
5.3 初始进入便显示头像
我们没有点击上传时头像仍为初始头像,我们可以将头像保存在cookie中。可以更新头像成功后,将服务器返回的头像路径保存在客户端cookie对象,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动检测去读取cookie中头像并设到src属性上。
1.导入cookie.js
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
2.调用cookie方法
//将返回用户的头像信息保存至cookie中。expires: 7表示存活时间7天。
$.cookie("avatar",json.data.avatar,{expires: 7})
3. 在upload中调用cookie,注意要先导入cookie.js
在这里我也遇到了上传不成功的问题,通过url测试是可以拿到数据的,我写了两个console.log也没有再控制台输出,因此可以初步判定为js代码未识别问题。
5.4 同步更新头像
再保存好头像后再次想cookie中存储新的头像信息,将以前的avatar数据顶掉。
$.cookie("avatar",json.data,{expires: 7})
5.5 解决重启服务后临时文件路径改变导致之前存储路径失效问题(!非常重要)
解决措施:
- 在idea的启动配置里面配置工作区。
然后再工作目录下创建public文件夹,之后的 工作目录便会采用这个文件,完美解决了临时文件路径问题。