目录
1. 修改用户头像
1.1 获取原来的用户头像和用户名
1.2 实现保存头像
2. 修改用户名或密码
1. 修改用户头像
本文是针对之前的一篇项目博客 - 博客系统 做的一个扩展功能.
1.1 获取原来的用户头像和用户名
想要修改头像, 那么就得先获取数据库中原来的头像, 此处顺便将原用户名一起获取并展示出来. 原密码最好不要获取出来, 因为有可能你在修改密码的中途, 你去上厕所了, 然后你的密码被你好兄弟给修改了.
大致效果 :
前端展示页面相关代码
<div class="row" style="margin-top: 70px;margin-bottom: 50px;">
<img id="photo" src="img/bg2.jpg"
style="width: 100px;height: 115px;">
<input id="file" style="font-size: 12px;width: 120px;" type="file">
<button style="width: 80px;height: 50px;" onclick="savePhoto()">保存</button>
</div>
<div class="row">
<span>用户名</span>
<input type="text" id="username">
</div>
<div class="row">
<span>原密码</span>
<input type="password" id="old_password">
</div>
<div class="row">
<span>新密码</span>
<input type="password" id="password">
</div>
<div class="row">
<span>确认密码</span>
<input type="password" id="password2">
</div>
<div class="row" style="margin-top: 10px;">
<button id="submit" onclick="updateUser()">修 改</button>
</div>
写前端 js 代码
// 获取用户头像和昵称
function initPage() {
jQuery.ajax({
url:"/user/myinfo",
type:"GET",
data:{},
success:function(body) {
if(body.code==200 && body.data!=null && body.data.id>=0) {
// 得到了当前的 userinfo
var userinfo = body.data
if(userinfo.photo!=null && userinfo.photo!="") {
jQuery("#photo").attr("src",userinfo.photo);
}
jQuery("#username").val(userinfo.username);
} else {
alert("抱歉: 查询用户信息出错, 请刷新页面再试! " + body.msg);
}
}
});
}
initPage();
1. 获取当前登录人的用户名和头像, 只需在后端的 session 中拿到具体的 userinfo 即可.
2. sucess 回调函数中的注意事项: 因为我们在构造数据 (userinfo) 的时候, 头像一般都是写死的本地图片, 所以新用户一般都是默认头像, 所以我们在操作 dom 树构造 photo 的 src 属性时, 一定要判断 photo 是否为 null 或者是否为 "", 如果是就不要设置 photo 的 src 属性, 否则会导致用户没有头像.
写后端代码
@RequestMapping("/myinfo")
public Object myInfo(HttpServletRequest request) {
// 从 session 工具类中拿用户登录信息
UserInfo userInfo = SessionUtil.getLoginUser(request);
if (userInfo == null || userInfo.getId() <= 0) {
return AjaxResult.fail(-2, "当前用户未登录!");
}
return AjaxResult.success(userInfo);
}
因为多处代码需要拿 session , 所以将其封装成了一个公共的方法.
1.2 实现保存头像
此处我们上传新的头像后, 并点击保存按钮时, 就是修改头像成功了.
写前端 js 代码 (给保存按钮添加点击事件)
// 保存头像
function savePhoto() {
// 得到图片
var photo = jQuery("#file")[0].files[0];
if (photo == null) {
alert("请先选择要上传的头像!");
return false;
}
// 构建一个 form 表单
var formData = new FormData();
formData.append("file", photo);
jQuery.ajax({
url:"/user/save_photo",
type:"POST",
data:formData,
processData:false, // 告诉 jQuery 不要去加工数据
contentType:false, // 告诉 jQuery 不要设置类型
success:function(body) {
if(body.code==200 && body.data!=null && body.data!="") {
jQuery("#photo").attr("src",body.data);
} else {
// 图片上传失败
alert("抱歉: 上传图片失败, 请重试! " + body.msg);
}
}
});
}
1. 此处的得到图片代码比较特殊
2. 发送 ajax 时, 参数是发送一个 form 表单给后端, 所以 ajax 中需要多添加两个参数 : processData 和 contentType.
3. 表单传给后端时, 后端针对图片生成一个网络地址映射到本地保存的地址, 然后将网络地址返回给前端, 前端操作 dom 树将其设置给 photo 的 src 属性.
写后端代码
1. 配置映射图片的路径
在配置文件中 application.properties 指定保存头像的本地路径 :
imagepath=D:/image/
在添加拦截规则的类里边加上 addResourceHandlers 类 :
@Value("${imagepath}")
private String imagepath;
**
* 映射图片路径
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/image/**")
.addResourceLocations("file:" + imagepath + "/");
}
2. 保存图片到服务器, 保存图片地址到数据库
@RequestMapping("/save_photo")
public Object savePhoto(MultipartFile file, HttpServletRequest request) {
// 1.保存图片到服务器
// 得到图片的后缀
String imageType = file.getOriginalFilename().
substring(file.getOriginalFilename().lastIndexOf("."));
// 生成图片的名称
String imgName = UUID.randomUUID().toString()
.replace("-", "") + imageType;
try {
file.transferTo(new File(imagePath + imgName));
} catch (IOException e) {
return AjaxResult.fail(-1, "图片上传失败!");
}
String imgUrl = "/image/" + imgName;
// 2.将图片地址保存到数据库
UserInfo userInfo = SessionUtil.getLoginUser(request);
if(userInfo == null || userInfo.getId() <= 0) {
return AjaxResult.fail(-2, "请先登录! ");
}
// 修改头像
int result = userService.updatePhoto(userInfo.getId(),imgUrl);
if(result == 1) {
// 将用户的头像更新到 session 中
userInfo.setPhoto(imgUrl);
HttpSession session = request.getSession();
session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
return AjaxResult.success(imgUrl);
} else {
return AjaxResult.fail(-3, "数据库修改失败! ");
}
}
【步骤分析】
1. 保存图片到服务器
当项目部署云服务器时, 用户访问项目并修改头像时, 是从用户的电脑上选取了一张图片上传到后端, 那么后端需要将这张图片保存到云服务器的一个本地路径, 然后再生成图片对应的网络地址. 最后将新头像对应的网络地址返回给前端, 前端就可以通过设置头像 photo 对应的 src 属性为返回的网络地址.
2. 保存图片到数据库
修改头像不仅要更新当前页面展示的头像, 数据库中的头像对应的网络地址也要更新, 另外如果更新数据成功了, 要同时更新 session 中的 userinfo 信息. 因为博客列表页的当前用户的身份信息, 后端都是从 session 中取出来返回给前端的, 所以需要同时更新数据库和 session 中的 photo 字段(属性).
此时修改头像操作就已经大功告成了~
2. 修改用户名或密码
此处我们点击个人中心跳转到修改用户信息的页面时, 它只是将头像和用户名展示出来了, 而原密码, 新密码和确认密码需要手动输入(要么三个都不为空 - 改, 要么都为空 - 不改).
- 如果不修改密码, 只是修改用户名或者都不修改, 然后点击修改按钮, 就提示修改成功, 并跳转到我的内容管理页面.
- 如果修改了密码, 并点击了修改按钮, 就提示修改成功, 并强制用户重新登录.
写前端 js 代码(给修改按钮添加点击事件)
function updateUser() {
var isUpdatePassword = false; // 是否修改密码
// 1.非空效验
var username = jQuery("#username");
var oldPassword = jQuery("#old_password");
var password = jQuery("#password");
var password2 = jQuery("#password2");
if (username.val().trim() == "") {
alert("请先输入新用户名!");
username.focus();
return false;
}
if (oldPassword.val() != "" ||
password.val() != "" || password2.val() != "") {
// 需要修改密码
isUpdatePassword = true;
if (oldPassword.val().trim() == "") {
alert("请先输入原密码!");
oldPassword.focus();
return false;
}
if (password.val().trim() == "") {
alert("请先输入新密码!");
password.focus();
return false;
}
if (password2.val().trim() == "") {
alert("请先输入确认密码!");
password2.focus();
return false;
}
// 判断新密码和确认密码是否一致
if (password.val() != password2.val()) {
alert("两次输入的新密码不一致,请先确认!");
return false;
}
}
// 2.将前端的数据提交给后端
jQuery.ajax({
url: "/user/update",
type: "POST",
data: {
"username": username.val(),
"oldPassword": oldPassword.val(),
"password": password.val(),
"isUpdatePassword": isUpdatePassword
},
success: function (res) {
// 3.将返回的结果展现给用户
if (res.code == 200 && res.data == 1) {
// 修改成功
if (isUpdatePassword) {
alert("修改成功,请重新登录!");
// 修改密码,重新登录
location.href = "login.html";
} else {
alert("修改成功!");
location.href = "myblog_list.html";
}
} else {
// 修改失败
alert("抱歉:修改失败,请重试!" + res.msg);
}
}
});
}
写后端代码
@RequestMapping("/update")
public Object update(String username, String oldPassword, String password,
Boolean isUpdatePassword, HttpServletRequest request) {
// 1.参数效验
if(!StringUtils.hasLength(username)) {
return AjaxResult.fail(-1, "非法参数! ");
}
// 是否要修改密码
if(isUpdatePassword) {
// 修改原密码
if(!StringUtils.hasLength(oldPassword) || !StringUtils.hasLength(password)) {
return AjaxResult.fail(-1, "非法参数! ");
}
}
// 2.组装数据 (从 session 中获取用户信息)
UserInfo userInfo = SessionUtil.getLoginUser(request);
if(userInfo == null || userInfo.getId() <= 0) {
return AjaxResult.fail(-2,"请先登录! ");
}
UpdateWrapper<UserInfo> wrapper = new UpdateWrapper<>();
// 判断两次密码是否一致
if(isUpdatePassword) {
// 验证原密码和 session 中的密码是否一致
UserInfo dbUser = userService.getById(userInfo.getId());
boolean checkPassword = SaltSecurityUtil.decrypt(oldPassword,dbUser.getPassword());
if(!checkPassword) {
return AjaxResult.fail(-3,"原密码输入错误! ");
}
// 修改密码
password = SaltSecurityUtil.encrypt(password);
wrapper.set("password",password);
}
// 3.修改数据库
wrapper.eq("id",userInfo.getId());
wrapper.set("username", username);
boolean result = userService.update(wrapper);
// 更新 session 中的用户名
if(result) {
userInfo.setUsername(username);
HttpSession session = request.getSession();
session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
}
// 4.将结果返回给前端
return AjaxResult.success(result ? 1 : 0);
}
【步骤分析】
1. 非空效验
前端传递了新用户名, 原密码, 新密码, 以及是否修改了密码的标志, 于是在做判断时, 如果只修改了用户名, 就可以使用 isUpdatePassword 标志位来跳过更新数据密码的操作. 否则都要进行修改.
2. 组装数据
组装好一个新的 userinfo (新的用户名或密码), 为更新数据库操作提供数据源, 此处更新密码成功的前提是原密码和数据库密码要保持一致, 而数据库中存储的是加密后的密码, 所以需要先拿着原密码和数据库中的密码去调用解密方法, 得到一个 boolean 类型的值, 再根据这个布尔值来判断是否要进行修改操作.
【注意】session 中的对象存储机制 >>
由于我们是可以拿到当前用户的 session, 所以想要拿数据库中存储的密码, 我就会想着去 session 中拿到 userinfo, 再去拿到对应的密码, 这确实挺方便. 但是我在实现登录页面时, 登录成功后并将 session 存储 redis, 但是在返回数据给前端之前, 我执行了将密码置为空字符串这一操作, 因为密码如果通过网络传输返回给前端, 是不安全的.
<问题的出处> 正因为我的这一步置空字符串操作, 就会导致 session 中的密码也变成了空字符串. 这是为什么呢 ??
因为 session 的底层是用 concurrentHashMap 来保存数据的, 而 map 中并没有直接存储新的对象, 而是存储了对象的引用, 也就是 userinfo 的引用, 虽然我是在设置密码为空之前就将 userinfo 存储 session 了, 但是这也同样影响了 session 中的 password 了. 此时 session 中的 password 已经是空字符串了.
再回到调用解密方法这一步, 我们就不能拿着原密码和 session 中的密码去调用解密码方法了, 而是需要拿着从 session 获取到的 userinfo 中的用户 id, 去查数据库得到一个新的 userinfo, 此时这个 userinfo 的密码才不为空, 才可以拿着它的 password 去和原密码去调用解密方法.
3. 修改数据库
经过了第二步的组装数据, 第三步就变得简单了, 只需要使用 MP 进行修改操作即可, 但是在进行修改操作时, 修改后的用户名或密码最好设置在 updatewrapper 中, 然后只传一个 wrapper 对象. 如果将修改后的用户名或密码设置给 session 中的 userinfo, 然后再给 MP 传两个参数 (userinfo, wrapper), 那么有可能造成不必要的参数覆盖问题.
另外就是修改完数据库之后, 要及时更新 session 中的用户名, 因为如果只修改了用户名, 不修改密码, 修改完成后会跳转博客列表页, 而博客列表页的用户身份信息都是从后端的 session 中来的, 如果不及时更新 session 的话, 那么在你下次重新登录之前, 用户名都不会变.
上述的 session 中的对象存储机制是参考这篇文章得出的结论 - 为何session中存入对象后,修改对象的属性值后并没有再次存入session,session中存放的对象也发生改变?
到此为止呢, 修改头像, 修改用户名或密码就全部实现完成了~