• 【电商项目实战】上传头像(详细篇)


    🍁博客主页:👉@不会压弯的小飞侠
    欢迎关注:👉点赞👍收藏留言
    系列专栏:👉SpringBoot电商项目实战
    学习社区: 👉不会压弯的小飞侠
    知足上进,不负野心。
    🔥欢迎大佬指正,一起学习!一起加油!

    在这里插入图片描述


    🍁上传头像-持久层

    • 上传文件的操作其实是:先将用户上传的文件保存到服务器端的某个位置,然后将保存文件的路径记录在数据库中。当后续需要使用该文件时,从数据库中读出文件的路径,即可实现在线访问该文件。
    • 在持久层处理数据库中的数据时,只需要关心如何记录头像文件的路径,并不需要考虑上传时保存文件的过程

    🔥 接口与抽象方法

    • 在UserMapper接口中添加updateAvatarByUid()抽象方法。
     /**
         * 根据uid更新用户的头像
         * @param uid 用户的id
         * @param avatar 新头像的路径
         * @param modifiedUser 修改执行人
         * @param modifiedTime 修改时间
         * @return 受影响的行数
         */
        Integer updateAvatarByUid(
                @Param("uid") Integer uid,
                @Param("avatar") String avatar,
                @Param("modifiedUser") String modifiedUser,
                @Param("modifiedTime") Date modifiedTime);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    🔥 配置SQL映射

    • 在UserMapper.xml中配置updateAvatarByUid()抽象方法的映射。
     <!-- 根据uid更新用户的头像-->
        <update id="updateAvatarByUid">
            UPDATE
                t_user
            SET
                avatar = #{avatar},
                modified_user = #{modifiedUser},
                modified_time = #{modifiedTime}
            WHERE
                uid = #{uid}
        </update>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🔥 测试

    • 在UserMapperTests中编写并执行单元测试。
    @Test
        public void updateAvatarByUid() {
            Integer uid = 11;
            String avatar = "/upload/avatar.png";
            String modifiedUser = "管理员";
            Date modifiedTime = new Date();
            Integer rows = userMapper.updateAvatarByUid(uid, avatar, modifiedUser, modifiedTime);
            System.out.println("rows=" + rows);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 测试结果

    在这里插入图片描述

    🍁上传头像-业务层

    🔥 接口与抽象方法

    • 在IUserService中添加changeAvatar(Integer uid, String username, String avatar)抽象方法。
     /**
         * 修改用户头像
         * @param uid 当前登录的用户的id
         * @param username 当前登录的用户名
         * @param avatar 用户的新头像的路径
         */
        void changeAvatar(Integer uid, String username, String avatar);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    🔥 实现抽象方法

    • 在UserServiceImpl类中实现changeAvatar(Integer uid, String username, String avatar)方法。
    @Override
        public void changeAvatar(Integer uid, String username, String avatar) {
            // 调用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("用户数据不存在");
            }
    
            // 创建当前时间对象
            Date now = new Date();
            // 调用userMapper的updateAvatarByUid()方法执行更新,并获取返回值
            Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, now);
            // 判断以上返回的受影响行数是否不为1
            if (rows != 1) {
                // 是:抛出UpdateException
                throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    🔥 测试

    • 在UserServiceTests类中进行单元测试。
    @Test
        public void changeAvatar() {
                Integer uid = 11;
                String username = "lll";
                String avatar = "/upload/change.png";
                iUserService.changeAvatar(uid, username, avatar);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 测试结果

    在这里插入图片描述

    🍁上传头像-控制层

    🔥 异常

    • 在处理上传文件的过程中,用户可能会选择错误的文件上传,此时就应该抛出对应的异常并进行处理。所以需要创建文件上传相关异常的基类,即在com.jkj.controller.ex包下创建FileUploadException类,并继承自RuntimeException类。
    /** 文件上传相关异常的基类 */
    public class FileUploadException extends RuntimeException {
        public FileUploadException() {
            super();
        }
    
        public FileUploadException(String message) {
            super(message);
        }
    
        public FileUploadException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public FileUploadException(Throwable cause) {
            super(cause);
        }
    
        protected FileUploadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 在处理上传的文件过程中,经分析可能会产生以下异常。这些异常类都需要继承自FileUploadException类。
    // 上传的文件为空
    com.jkj.controller.ex.FileEmptyException
    // 上传的文件大小超出了限制值
    com.jkj.store.controller.ex.FileSizeException
    // 上传的文件类型超出了限制
    com.jkj.store.controller.ex.FileTypeException
    // 上传的文件状态异常
    com.jkj.store.controller.ex.FileStateException
    // 上传文件时读写异常
    com.jkj.store.controller.ex.FileUploadIOException
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 按照如下规则编写上面这五个异常类
    • 创建FileEmptyException异常类,并继承FileUploadException类。
    • 创建FileSizeException异常类,并继承FileUploadException类。
    • 创建FileTypeException异常类,并继承FileUploadException类。
    • 创建FileStateException异常类,并继承FileUploadException类。
    • 创建FileUploadIOException异常类,并继承FileUploadException类。
    /** 上传的文件为空的异常,例如没有选择上传的文件就提交了表单,或选择的文件是0字节的空文件 */
    public class FileEmptyException extends FileUploadException {
        public FileEmptyException() {
            super();
        }
    
        public FileEmptyException(String message) {
            super(message);
        }
    
        public FileEmptyException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public FileEmptyException(Throwable cause) {
            super(cause);
        }
    
        protected FileEmptyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 在BaseController的handleException()的@ExceptionHandler注解中添加FileUploadException.class异常的处理;最后在方法中处理这些异常。
    @ExceptionHandler({ServiceException.class, FileUploadException.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);
    	} 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);
    	}
    	
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    🔥 用户提交的请求

    • 请求路径:/users/change_avatar
    • 请求参数:MultipartFile file, HttpSession session
    • 请求类型:POST
    • 响应结果:JsonResult

    🔥 处理请求

    • 在UserController类中添加处理请求的changeAvatar(@RequestParam(“file”) MultipartFile file, HttpSession session)方法。
     /** 头像文件大小的上限值(10MB) */
        public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024;
        /** 允许上传的头像的文件类型 */
        public static final List<String> AVATAR_TYPES = new ArrayList<String>();
    
        /** 初始化允许上传的头像的文件类型 */
        static {
            AVATAR_TYPES.add("image/jpeg");
            AVATAR_TYPES.add("image/png");
            AVATAR_TYPES.add("image/bmp");
            AVATAR_TYPES.add("image/gif");
        }
    
        @PostMapping("change_avatar")
        public JsonResult<String> changeAvatar(@RequestParam("file") MultipartFile file, HttpSession session) {
            // 判断上传的文件是否为空
            if (file.isEmpty()) {
                // 是:抛出异常
                throw new FileEmptyException("上传的头像文件不允许为空");
            }
    
            // 判断上传的文件大小是否超出限制值
            if (file.getSize() > AVATAR_MAX_SIZE) { // getSize():返回文件的大小,以字节为单位
                // 是:抛出异常
                throw new FileSizeException("不允许上传超过" + (AVATAR_MAX_SIZE / 1024) + "KB的头像文件");
            }
    
            // 判断上传的文件类型是否超出限制
            String contentType = file.getContentType();
            // public boolean list.contains(Object o):当前列表若包含某元素,返回结果为true;若不包含该元素,返回结果为false。
            if (!AVATAR_TYPES.contains(contentType)) {
                // 是:抛出异常
                throw new FileTypeException("不支持使用该类型的文件作为头像,允许的文件类型:\n" + AVATAR_TYPES);
            }
    
            // 获取当前项目的绝对磁盘路径
            String parent = session.getServletContext().getRealPath("upload");
            // 保存头像文件的文件夹
            File dir = new File(parent);
            if (!dir.exists()) {
                dir.mkdirs();
            }
    
            // 保存的头像文件的文件名
            String suffix = "";
            String originalFilename = file.getOriginalFilename();
            int beginIndex = originalFilename.lastIndexOf(".");
            if (beginIndex > 0) {
                suffix = originalFilename.substring(beginIndex);
            }
            String filename = UUID.randomUUID().toString() + suffix;
    
            // 创建文件对象,表示保存的头像文件
            File dest = new File(dir, filename);
            // 执行保存头像文件
            try {
                file.transferTo(dest);
            } catch (IllegalStateException e) {
                // 抛出异常
                throw new FileStateException("文件状态异常,可能文件已被移动或删除");
            } catch (IOException e) {
                // 抛出异常
                throw new FileUploadIOException("上传文件时读写错误,请稍后重尝试");
            }
    
            // 头像路径
            String avatar = "/upload/" + filename;
            // 从Session中获取uid和username
            Integer uid = getUidFromSession(session);
            String username = getUsernameFromSession(session);
            // 将头像写入到数据库中
            userService.changeAvatar(uid, username, avatar);
    
            // 返回成功头像路径
            return new JsonResult<String>(OK, avatar);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 启动项目,打开浏览器先登录,再访问http://localhost:8080/web/upload.html进行测试。

    在这里插入图片描述

    🍁上传头像-前端页面

    🔥设置上传文件大小

    • SpringBoot中默认MultipartResolver的最大文件大小值为1M。如果上传的文件的大小超过1M,会抛FileSizeLimitExceededException异常。
    • 如果需要调整上传的限制值,直接在启动类中添加getMultipartConfigElement()方法,并且在启动类之前添加@Configuration注解。
    @Bean
    	public MultipartConfigElement getMultipartConfigElement() {
    		MultipartConfigFactory factory = new MultipartConfigFactory();
    		// DataSize dataSize = DataSize.ofMegabytes(10);
    		// 设置文件最大10M,DataUnit提供5中类型B,KB,MB,GB,TB
    		factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
    		factory.setMaxRequestSize(DataSize.of(10, DataUnit.MEGABYTES));
    		// 设置总上传数据总大小10M
    		return factory.createMultipartConfig();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 还可以通过在application.properties中添加配置来实现。
    spring.http.multipart.max-file-size=10MB
    spring.http.multipart.max-request-size=10MB
    
    • 1
    • 2

    🔥 前端页面BUG解决

    • 头像上传成功后,显示上传的头像。在upload.html页面中,是使用img标签来显示头像图片的。首先确定img标签是否添加有id="img-avatar"属性,便于后续访问该标签;而img标签是通过src属性来决定显示哪张图片的,所以修改src该属性的值即可设置需要显示的图片。修改表单添加id="form-change-avatar"属性。修改input标签,添加id="btn-change-avatar"和type="button"属性。
    • 在upload.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。
      • processData:处理数据。默认情况下,processData的值是true,其代表以对象的形式 上传的数据都会被转换为字符串的形式上传。而当上传文件的时候,则不需要把其转换为字符串,因此要改成false。
      • contentType:发送数据的格式。其代表的是前端发送数据的格式,默认值application/x-www-form-urlencoded。代表的是ajax的 data是以字符串的形式传递,使用这种传数据的格式,无法传输复杂的数据,比如多维数组、文件等。把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]),
                dataType: "JSON",
                processData: false, // processData处理数据
                contentType: false, // contentType发送数据的格式
                success: function(json) {
                    if (json.state == 200) {
                        $("#img-avatar").attr("src", json.data);
                    } else {
                        alert("修改失败!" + json.message);
                    }
                },
                error: function(xhr) {
                    alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
                    location.href = "login.html";
                }
            });
    	});
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    🔥 登录后显示头像

    • 当用户登录成功后,将服务器返回的头像路径存储到本地的Cookie中,在打开“上传头像”页面时,从本地的Cookie中读取头像路径并显示即可。在登录login.html页面中,当登录成功后,将用户头像路径保存到Cookie中。
    $("#btn-login").click(function() {
        $.ajax({
            url: "/users/login",
            type: "POST",
            data: $("#form-login").serialize(),
            dataType: "json",
            success: function(json) {
                if (json.state == 200) {
                    alert("登录成功!");
                    $.cookie("avatar", json.data.avatar, {expires: 7});
                    console.log("cookie中的avatar=" + $.cookie("avatar"));
                    location.href = "index.html";
                } else {
                    alert("登录失败!" + json.message);
                }
            }
        });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 语法:$.cookie(名称,值,[option])。[option]参数说明:

      • expires:有限日期,可以是一个整数或一个日期(单位天)。如果不设置这个值,默认情况下浏览器关闭之后此Cookie就会失效。
      • path:表示Cookie值保存的路径,默认与创建页路径一致。
      • domin:表示Cookie域名属性,默认与创建页域名一样。要注意跨域的概念,如果要主域名二级域名有效则要设置“.xxx.com”。
      • secrue:布尔类型的值,表示传输Cookie值时,是否需要一个安全协议。
    • 在upload.html页面中,默认并没有引用jqueyr.cookie.js文件,因此无法识别$.cookie()函数;所以需要在upload.html页面head标签内添加jqueyr.cookie.js文件。

    <script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
    
    • 1
    • 在打开页面时自动读取显示用户图像。获取Cookie中头像的路径,然后将获取到的头像路径设置给img标签的src属性以显示头像。在upload.html页面中的script标签的内部添加自动读取用户图像的jquery代码。
    $(document).ready(function () {
        console.log("cookie中的avatar=" + $.cookie("avatar"));
        $("#img-avatar").attr("src", $.cookie("avatar"));
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🔥 显示最新头像

    • 每次打开页面时,读取Cookie中的头像并显示”,如果此时重新上传用户头像,而Cookie中所保存的头像还是之前上传的头像路径值,无法显示最新的用户头像。所以当用户重新上传头像后,还应把新头像的路径更新到Cookie中。
    • 在upload.html页面中,用户头像修改成功后,并将新的用户头像路径保存到Cookie中。
    $.cookie("avatar", json.data, {expires: 7});
    
    • 1
    • 最终代码
    <script type="text/javascript">
    			$(document).ready(function () {
    				console.log("cookie中的avatar=" + $.cookie("avatar"));
    				$("#img-avatar").attr("src", $.cookie("avatar"));
    			});
    
    			$("#btn-change-avatar").click(function() {
    				$.ajax({
    					url: "/users/change_avatar",
    					type: "POST",
    					data: new FormData($("#form-change-avatar")[0]),
    					dataType: "JSON",
    					processData: false, // processData处理数据
    					contentType: false, // contentType发送数据的格式
    					success: function(json) {
    						if (json.state == 200) {
    							$("#img-avatar").attr("src", json.data);
    							$.cookie("avatar", json.data, {expires: 7});
    						} else {
    							alert("修改失败!" + json.message);
    						}
    					},
    					error: function(xhr) {
    						alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
    						location.href = "login.html";
    					}
    				});
    			});
    		</script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 启动项目进行测试
      在这里插入图片描述

    学习视频:

    SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战-哔哩哔哩】
    https://b23.tv/qGh9x9L

    在这里插入图片描述

  • 相关阅读:
    单片机——将P1口状态送入P0、P2和P3口
    工作之外看点书籍的一点体会
    混沌系统在图像加密中的应用(随机性测试之FIPS 140-2测试和SP800-22测试)
    Qt通过Doc模式读取XML并设计一个增删改查方便的一个操作类
    AI人工智能入门-概念介绍和学习路线
    谈谈TIME_WAIT
    web前端-javascript-逻辑运算符(! 非取反,短路的&& 与,短路的|| 或,&& || 非布尔值的情况,对于非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,并且返回)
    Powershell基础
    15分钟教你从0到1,水出SCI(精品),学术裁缝必修课_来自B站水论文的程序猿
    高手系列!数据科学家私藏pandas高阶用法大全 ⛵
  • 原文地址:https://blog.csdn.net/qq_43514330/article/details/126510705