个人中心 - 实现修改用户头像、用户名或密码

目录

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中存放的对象也发生改变?

到此为止呢, 修改头像, 修改用户名或密码就全部实现完成了~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/58301.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis的安装

Redis的安装 1、Windows下的安装 1.1 下载 下载地址&#xff1a; https://github.com/MicrosoftArchive/redis/releases https://github.com/tporadowski/redis/releases Redis支持 32 位和 64 位&#xff0c;这个需要根据你系统平台的实际情况选择&#xff0c;这里我们下…

Prometheus实现系统监控报警邮件

Prometheus实现系统监控报警邮件 简介 Prometheus将数据采集和报警分成了两个模块。报警规则配置在Prometheus Servers上&#xff0c; 然后发送报警信息到AlertManger&#xff0c;然后我们的AlertManager就来管理这些报警信息&#xff0c;聚合报警信息过后通过email、PagerDu…

Java并发编程之顺序一致性

如果程序是正确同步的&#xff0c;程序的执行将具有顺序一致性&#xff08;Sequentially Consistent&#xff09;——即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。 同步&#xff0c;即排队。 同一时刻&#xff0c;只能有一个线程和内存交互&#xff01;&a…

21.Netty源码之编码器

highlight: arduino-light Netty如何实现自定义通信协议 在学习完如何设计协议之后&#xff0c;我们又该如何在 Netty 中实现自定义的通信协议呢&#xff1f;其实 Netty 作为一个非常优秀的网络通信框架&#xff0c;已经为我们提供了非常丰富的编解码抽象基类&#xff0c;帮助我…

【Mybatis】XML映射文件

目录 11.3XML映射文件 1.select 2.insert、update、delete 3.Sql 4.parameters(参数) 5.resultMap 6.resultMap 使用示例 (1)在先前创建的数据库stu中创建表student 2&#xff0c;并插入若干条数据&#xff0c;代码如下&#xff1a; (2)创建工程mybatis_ResultMap_demo。 (…

【Lua学习笔记】Lua进阶——垃圾回收

按照唐老师的课程本来要讲自带库的&#xff0c;但是想想这东西能看文档&#xff0c;ctrl左键还能看注解&#xff0c;并且最重要的许多自带库的方法基本大部分语言都有&#xff0c;其实看看就能懂了。所以还是重点讲讲垃圾回收 文章目录 GC辅助垃圾回收collectgarbage增量模式分…

【雕爷学编程】MicroPython动手做(32)——物联网之MQTT

MQTT &#xff08;Message Queuing Telemetry Transport&#xff09;消息队列遥测传输协议&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;由IBM在1999年发布。M…

2023 电赛 E 题 激光笔识别有误--使用K210/Openmv/树莓派/Jetson nano实现激光笔在黑色区域的目标检测

1. 引言 1.1 激光笔在黑色区域目标检测的背景介绍 在许多应用领域&#xff0c;如机器人导航、智能家居和自动驾驶等&#xff0c;目标检测技术的需求日益增加。本博客将聚焦于使用K210芯片实现激光笔在黑色区域的目标检测。 激光笔在黑色区域目标检测是一个有趣且具有挑战性的…

cpolar内网穿透外网远程访问本地网站

cpolar内网穿透外网远程访问本地网站 文章目录 cpolar内网穿透外网远程访问本地网站 在现代人的生活中&#xff0c;电脑是离不开的重要设备&#xff0c;大家看到用到的各种物品都离不开电脑的支持。尽管移动电子设备发展十分迅速&#xff0c;由于其自身存在的短板&#xff0c;使…

css, resize 拖拉宽度

效果如下&#xff1a; 可直接复制预览查看属性值: 关键样式属性&#xff1a; resize: horizontal; overflow-x: auto; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content…

Qt应用开发(基础篇)——数值微调输入框QAbstractSpinBox、QSpinBox、QDoubleSpinBox

目录 一、前言 二、QAbstractSpinBox类 1、accelerated 2、acceptableInput 3、alignment 4、buttonSymbols 5、correctionMode 6、frame 7、keyboardTracking 8、readOnly 9、showGroupSeparator 10、specialValueText 11、text 12、wrapping 13、信号 二、Q…

pytorch实战-图像分类(一)(数据预处理)

目录 1.导入各种库 2.数据预处理 2.1数据读取 2.2图像增强 3.构建数据网络 3.1网络构建 3.2读取标签对应的名字 4.展示数据 4.1数据转换 4.2画图 5.模型训练 1.导入各种库 上代码&#xff1a; import os import matplotlib.pyplot as plt %matplotlib inline import nu…

一台电脑给另外一台电脑共享网络

这里写自定义目录标题 有网的电脑上操作一根网线连接两台电脑没网的电脑上 有网的电脑上操作 右键->属性->共享 如同选择以太网&#xff0c;勾选。确认。 一根网线连接两台电脑 没网的电脑上 没网的电脑为mips&麒麟V10 新增个网络配置ww&#xff0c;设置如下。 …

2.05 购物车后台刷新并显示

一.用户登录添加商品使用cookie存入购物车&#xff0c;并把购物车商品传入到后台 步骤1&#xff1a;创建购物车BO对象 public class ShopcartBO {private String itemId;private String itemImgUrl;private String itemName;private String specId;private String specName;p…

7.物联网操作系统互斥信号量

1.使用互斥信号量解决信号量导致的优先级反转&#xff0c; 2.使用递归互斥信号量解决互斥信号量导致的死锁。 3.高优先级主函数中多次使用同一信号量的使用&#xff0c;使用递归互斥信号量&#xff0c;但要注意每个信号量的使用要对应一个释放 优先级翻转问题 优先级翻转功能需…

牛客网Verilog刷题——VL48

牛客网Verilog刷题——VL48 题目答案 题目 在data_en为高期间&#xff0c;data_in将保持不变&#xff0c;data_en为高至少保持3个B时钟周期。表明&#xff0c;当data_en为高时&#xff0c;可将数据进行同步。本题中data_in端数据变化频率很低&#xff0c;相邻两个数据间的变化&…

【计算机视觉|人脸建模】SOFA:基于风格、由单一示例的2D关键点驱动的3D面部动画

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks 链接&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks | Proceedings of …

磁盘均衡器:HDFS Disk Balancer

HDFS Disk Balancer 背景产生的问题以及解决方法 hdfs disk balancer简介HDFS Disk Balancer功能数据传播报告 HDFS Disk Balancer开启相关命令 背景 相比较于个人PC&#xff0c;服务器一般可以通过挂载多块磁盘来扩大单机的存储能力在Hadoop HDFS中&#xff0c;DataNode负责最…

【数据结构与算法】线索化二叉树

线索化二叉树 n 个节点的二叉链表中含有 n 1 【公式 2n - (n - 1) n 1】个空指针域。利用二叉链表中的空指针域&#xff0c;存放指向该节点在某种遍历次序下的前驱和后继节点的指针&#xff08;这种附加的指针称为“线索”&#xff09;。这种加上了线索的二叉链表称为线索链…

网站是如何进行访问的?在浏览器地址栏输入网址并回车的一瞬间到页面能够展示回来,经历了什么?

这个问题是检验web和计网学习程度的经典问题。 网站访问流程&#xff1a; 1.域名->ip地址 1) 在输入完一个域名之后&#xff0c;首先是检查浏览器自身的DNS缓存是否有相应IP地址映射&#xff0c;如果没有对应的解析记录&#xff0c;浏览器会查找本机的hosts配置文件&…