SpringBoot项目--电脑商城【上传头像】

news2024/12/24 9:27:42

一、易错点

1.错误写法:

把文件存到数据库中,需要图片时访问数据库,数据库将文件解析为字节流返回,最后写到本地的某一个文件.这种方法太耗费资源和时间了

2.正确写法:

将对应的文件保存在操作系统上,然后再把这个文件路径记录下来,因为在记录路径的时候是非常便捷和方便的,将来如果要打开这个文件可以依据这个路径找到这个文件,所以说在数据库中保存该文件的路径即可.

3.注意点

稍微大一点的公司都会将所有的静态资源(图片,文件,其他资源文件)放到某台电脑上,再把这台电脑作为一台单独的服务器使用

2.持久层[Mapper]

1.SQL语句的规划

update t_user set avatar=?,modified_user=?,modified_time=? where uid=?

2. 设计接口和抽象方法

在UserMapper接口中定义一个抽象方法用于修改用户的头像

    /**
     * @param:是将映射文件中的#{}进行换名,如果不一致的时候
     * 根据用户uid值来最高用户的头像
     * @param uid
     * @param avatar
     * @param modifiedUser
     * @param modifiedTime
     * @return
     */
 /**
     * 注解@Param("SQL映射文件中#{}占位符的变量名"),解决的问题:
     * 当SQL语句的占位符和映射的接口方法参数名不一致时,需要将某个参数强行注入到某个
     * 占位符变量上时,可以使用@Param这个注解来标注映射的关系
     * */
    Integer updateAvatarByUid(@Param("uid") Integer uid,
                              @Param("avatar") String avatar,
                              @Param("modifiedUser") String modifiedUser,
                              @Param("modifiedTime") Date modifiedTime);
}

3. 编写映射

UserMapper.xml文件中编写映射的SQL语句

    <update id="updateAvatarByUid">
        update t_user
        set
            avatar=#{avatar},
            modified_time=#{modifiedTime},
            modified_user=#{modifiedUser}
        where
            uid=#{uid}

    </update>

4. 单元测试

@Test
public void updateAvatarByUid() {
    userMapper.updateAvatarByUid(
        11,
        "abc",
        "mxy",
        new Date());
}

3.业务层[Service]

1. 规划异常

  • 用户数据不存在,找不到对应的用户数据
  • 更新的时候,出现未知异常

无需重复开发

2. 设计接口和抽象方法及实现

1.先分析一下业务层接口需要哪些参数:那就需要看持久层接口要的有什么参数:

uid,avatar,modifiedUser,modifiedTime,其中modifiedTime是在方法中创建的,uid和modifiedUser从session中获取,但是session对象是在控制层的并不会出现在业务层,所以业务层要保留这两个参数,以便控制层可以传递过来

    /**
     * 修改用户头像
     * @param uid 用户id
     * @param avatar 用户头像的路径
     * @param username 用户名称
     */
    void changeAvatar(Integer uid,
                      String avatar,
                      String username);//业务层一般叫username而不叫modifiedUser,因
                                        // 为业务层并没有直接和数据库关联

2.编写业务层的更新用户头像的方法

    /**
     * 修改用户头像
     * @param uid 用户id
     * @param avatar 用户头像的路径
     * @param username 用户名称
     */
    @Override
    public void changeAvatar(Integer uid, String avatar, String username) {
        //查询当前用户的数据是否存在
        User result = userMapper.findByUid(uid);
        if(result==null || result.getIsDelete()==1){
            throw new UserNotFoundException("用户数据不存在");
        }
        Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
        if(rows!=1){
            throw new UpdateException("更新用户头像产生未知的异常");
        }

    }

3. 单元测试

@Test
public void changeAvatar() {
    userService.changeAvatar(11,"222","mmm");
}

4.控制层[Controller]

文件上传过程中产生的异常太多了,再比如文件类型不匹配或文件被损坏

1. 规划异常

客户端传递文件给服务器,服务器的控制端controller接收文件,接收时可能抛出异常,因为用户传过来的文件有可能超出了我们的大小限制

该异常能放在业务层抛出吗?没必要的,因为此时数据是从控制层往下传的,所以控制层产生的异常直接在这一层(控制层)抛就可以了

上传文件时的异常都是文件异常,所以可以先创建一个文件异常类的基类FileUploadException并使其继承RuntimeException

文件异常基类的子类有:

  • FileEmptyException:文件为空的异常(没有选择上传的文件就提交了表单,或选择的文件是0字节的空文件)
  • FileSizeException:文件大小超出限制
  • FileTypeException:文件类型异常(上传的文件类型超出了限制)
  • FileUploadIOException:文件读写异常
  • FileStateException:文件状态异常(上穿文件时该文件正在打开状态)

在controller包下创子包ex,在ex包里面创建文件异常类的基类和上述五个文件异常类,创建的六个类都重写其父类的五个构造方法

2. 处理异常

在基类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异常类不会被拦截到该方法中,点进@ExceptionHandler注解可以发现传参可以传数组类型,所以可以将异常统一处理方法上的注解改为:

@ExceptionHandler({ServiceException.class,FileUploadException.class})

3. 设计请求

  • /users/change_avatar
  • POST(GET请求提交数据只有2KB左右)
  • HttpSession session(获取uid和username),MultipartFile file
  • JsonResult<String>(不能是JsonResult<Void>:如果上传头像后浏览别的页面,然后再回到上传头像的页面就展示不出来了,所以图片一旦上传成功,就要保存该图片在服务器的哪个位置,这样的话一旦检测到进入上传头像的页面就可以通过保存的路径拿到图片,最后展示在页面上)
    @PostMapping("/change_avatar")
    public JsonResult<String> changeAvatar(HttpSession session,
                                           @RequestParam("file") MultipartFile file) {

        /**
         * 1.参数名为什么必须用file:在upload.html页面的147行<input type=
         * "file" name="file">中的name="file",所以必须有一个方法的参数名
         * 为file用于接收前端传递的该文件.如果想要参数名和前端的name不一
         * 样:@RequestParam("file")MultipartFile ffff:把表单中name=
         * "file"的控件值传递到变量ffff上
         * 2.参数类型为什么必须是MultipartFile:这是springmvc中封装的一个
         * 包装接口,如果类型是MultipartFile并且参数名和前端上传文件的name
         * 相同,则会自动把整体的数据包传递给file
         */

        //判断文件是否为空
        if (file.isEmpty()) {
            throw new FileEmptyException("文件为空");
        }
        if (file.getSize() > AVATAR_MAX_SIZE) {
            throw new FileSizeException("文件超出限制");
        }
        //判断文件的类型是否为我们规定的类型
        String contentType = file.getContentType();
        //如果集合包含某一个元素则返回true
        if (!AVATAR_TYPE.contains(contentType)) {
            throw new FileTypeException("文件类型不支持");
        }
        //上传的文件  ../upload/文件.png
        /**
         * session.getServletContext()获取当前Web应用程序的上下文
         * 对象(每次启动tomcat都会创建一个新的上下文对象)
         * getRealPath("/upload")的/代表当前web应用程序的根目录,通过该相
         * 对路径获取绝对路径,返回一个路径字符串,如果不能进行映射返回null,单
         * 斜杠可要可不要
         */
        String parent = session.getServletContext().getRealPath("upload");

        //File对象指向这个路径,File是否存在
        File dir = new File(parent);
        if (!dir.exists()) {//检测目录是否存在
            dir.mkdir();//创建当前的目录
        }
        //获取到这个文件名称,UUID工具来生成一个新的字符串作为文件名
        //例如:avatar01.png
        String originalFilename = file.getOriginalFilename();
        System.out.println("OriginalFilename=" + originalFilename);
        //获取文件后缀
        String suffix = "";
        int index = originalFilename.lastIndexOf(".");
        suffix = originalFilename.substring(index);
        String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
        //表示在dir这个文件下创建一个filename的文件
        File dest = new File(dir, filename);//此时dest是一个空文件
        //将参数file中数据写入到这个空文件中
        try {
            //将file文件中的数据写入到dest文件
            //transferTo是一个封装的方法,用来将file文件中的数据写入到dest文件
            file.transferTo(dest);
            /**
             * 先捕获FileStateException再捕获IOException是
             * 因为后者包含前者,如果先捕获IOException那么
             * FileStateException就永远不可能会被捕获
             */
        } catch (FileStateException e) {
            throw new FileStateException("文件状态异常");
        } catch (IOException e) {
            //这里不用打印e,而是用自己写的FileUploadIOException类并
            // 抛出文件读写异常
            throw new FileUploadIOException("文件读写异常");
        }
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        //返回头像的路径/upload/test.png
        String avater = "/upload/" + filename;
        userService.changeAvatar(uid, avater, username);
        //返回用户头像的路径给前端页面,将来用于头像展示
        return new JsonResult<>(OK, avater);
    }

5.前端页面

1.在upload.html的上传头像的表单加上三个属性:

action=“/users/change_avatar”
method=“post”(get请求提交数据只有2KB左右)
enctype=“multipart/form-data”(如果直接使用表单进行文件的上传,需要给表单加该属性,这样不会将目标文件的数据结构做修改后再上传,这不同于字符串,字符串随意切割修改也能拼在一起,但文件不行)


2.确认

<input type=“file” name=“file”>的type和name以及<input type=“submit” class=“btn btn-primary” value=“上传” />中的type

6.前端页面优化——修复bug

1 更改默认的大小限制

springmvc默认为1MB文件可以进行上传,如果刚好是1024*1024=1048576 bytes则会报代码错误,自己在控制层设置的public static final int AVATAR_MAX_SIZE = 10*1024*1024;需要在不超过原有大小的情况下才会起作用,所以要手动修改springmvc默认上传文件的大小

方法一:直接在配置文件中进行配置[application.yml]

# 修改文件上传大小
  servlet:
    multipart:
      max-file-size: 10MB # 表示上传的文件最大是多少
      # 整个文件是放在request中发生给服务器,请求当中还会有消息头等其他携带消息
      max-request-size: 15MB 

方法二:采用java代码的形式进行设置

1.该代码必须在主类中进行配置,因为主类是最早加载的,而配置文件必须是最早加载的

2.在主类中定义一个方法,方法名无所谓,但方法需要用@bean修饰,表示该方法返回值是一个bean对象,并且该bean对象被bean修饰,也就是这个方法返回了一个对象,然后把该对象交给bean管理,类似spring中的bean标签,含义是一样的,只是这里改为了注解

3.用@Configuration修饰主类使@bean注解生效,但其实@SpringBootApplication是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解的合并,所以可以不需要@Configuration

4.方法返回值是MultipartConfigElement类型,表示所要配置的目标的元素

@SpringBootApplication
@Configuration
//MapperScan注解指定当前项目中Mapper接口路径的位置,在项目启动的时候自动加载所有的接口
@MapperScan("com.example.mycomputerstore.mapper")
public class MyComputerStoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyComputerStoreApplication.class, args);
    }

    /**
     * 设置文件上传大小
     * @return
     */
    @Bean
    public MultipartConfigElement getMultipartConfigElement(){
        //创建一个配置的工厂类对象
        MultipartConfigFactory factory = new MultipartConfigFactory();

        //设置需要创建的对象的相关信息
        factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
        factory.setMaxRequestSize(DataSize.of(15,DataUnit.MEGABYTES));
        //通过工厂类来创建MulitpartConfigElement对象
        return factory.createMultipartConfig();
    }

}

2.上传后显示头像

上传头像成功后不能显示头像.

在页面中通过ajax请求来提交文件,提交完成后返回了json串,解析出json串中的data数据设置到img标签的src属性上

1.删掉在upload.html的上传头像的表单中加的三个属性

action=“/users/change_avatar”,method=“post”,enctype=“multipart/form-data”.加上id属性:id=“form-change-avatar”

2.将sumbit修改为button

把153行的input标签里面的type="submit"改为type=“button”(因为submit按钮不能添加事件,所以要改为普通的按钮)并加上属性id=“btn-change-avatar”

3.注意点:serialize用法【提交普通数据】/FormData类 的用法【提交文件】

1.serialize():可以将表单数据自动拼接成key=value的结构提交给服务器,一般提交的是普通的控件类型中的数据(type=text/password/radio/checkbox等等)

2.FormData类:将表单中数据保持原有的结构进行数据提交.文件类型的数据可以使用FormData对象进行存储

使用方法:new FormData($(“form”)[0]);

这行代码的含义是将id="form"的表单的第一个元素的整体值作为创建FormData对象的数据

3.虽然我们把文件的数据保护下来了,但是ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行数据提交.手动关闭这两个功能:

processData: false,//处理数据的形式,关闭处理数据
contentType: false,//提交数据的形式,关闭默认提交数据的形式

$("#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) {
						//attr(属性,属性值):给某一个属性设置某个值,给已有数据的属性再一次赋值
						$("#img-avatar").attr("src", json.data);
						//将头像保存在cookie
						//显示最新头像
						 $.cookie("avatar", json.data, {expires: 7});
					} else {
						alert("修改失败!" + json.message);
					}
				},
				error: function(xhr) {
					alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
					location.href = "login.html";
				}
			});
		});

4.登录后显示头像【使用Cookie进行存储】

将头像上传后会显示头像,但是关闭浏览器后再进入个人头像页面就不会显示头像了,因为只有点击"上传"才能发送ajax请求并显示头像.

可以在每次用户登录成功后将avatar保存在cookie中,登录的业务层返回给控制层user对象,该对象包含uid,username,avatar.所以要在登录页面login.html中将服务器返回的头像路径设置到cookie中,然后每次检测到用户打开上传头像页面,在这个页面中通过ready()方法来自动读取cookie中头像路径并设到src属性上

1.需要在login.html页面头部导入cookie.js文件

<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>

2.调用cookie方法保存路径

$.cookie(key,value,time);//time单位:天

在ajax请求原有的代码上加$.cookie(“avatar”,json.data.avatar,{expires: 7});

success: function(json) {
					if(json.state == 200) {
						alert("登录成功");
						//跳转到主页index.html
						//相对路径来确定跳转的页面
						location.href = "index.html";
						//将服务器返回头像设置到Cookie中
						//cookie(key,value,time)-->单位:天
						$.cookie("avatar",
								json.data.avatar,
								{expires:7})
					} else {
						alert("登录失败 -> 原因:" + json.message);
					}
				},

3.需要在upload.html获取cookie中的值,所以要在页面头部导入cookie.js文件

<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>

4.在upload.html的script标签中加ready()自动读取cookie数据

		//cookie获取头像
		$(document).ready(function () {
			console.log("cookie中的avatar=" + $.cookie("avatar"));
			//将cookie值获取出来设置到头像的src属性上
			$("#img-avatar").attr("src", $.cookie("avatar"));
		});

5.显示最新头像

上传头像后不重新登录而是浏览其他页面,然后再进入个人头像页面时展示的头像是上次上传的,因为此时cookie中的值是上次上传的头像的路径,所以需要上传头像后使用同名覆盖更改cookie中路径

在ajax函数的success属性值的if语句加:

$.cookie("avatar",json.data,{expires: 7});

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重生奇迹通关恶魔广场攻略篇

初次进入重生奇迹MU的血色城堡&#xff0c;你可能会对里面不断刷新出来的怪物产生一种密集恐惧症&#xff0c;但是请相信一点&#xff0c;那就是恶魔广场里面的怪物更多&#xff0c;而且品种还不重复&#xff0c;由低至高&#xff0c;轮番刷新&#xff0c;一波又一波……在我看…

Linux部署kettle并设置定时任务

一.安装Kettle linux中使用kettle时首先需要jdk环境&#xff0c;这里就不概述linux中jdk的安装与配置了。 1.首先将kettle压缩包放入linux并解压 unzip data-integration.zip kettle安装路径为:/root/Kettle9.3/data-integration 设置权限 chmod -R 755 /root/Kettle9.3/d…

Allegro画原理图时不能用的非法字符,你知道吗?

Cadence Allegro是一款电子设计自动化工具&#xff0c;常用于原理图绘制和电路设计&#xff0c;在使用Allegro画原理图时&#xff0c;电子工程师可能为了确保文件的准确性和稳定性&#xff0c;能够顺利进行后续的PCB设计和制造&#xff0c;需要注意这些非法字符&#xff0c;那么…

提高 Web 开发效率的10个VS Code扩展插件,你知道吗?

前言 一个出色的开发工具可以显著提高开发人员的开发效率&#xff0c;而优秀的扩展插件则能更进一步地提升工具的效率。在前端开发领域&#xff0c;VSCode毫无疑问是目前最受欢迎的开发工具。为了帮助前端开发人员提高工作效率&#xff0c;今天小编将向大家推荐10个强大的VSCo…

TSINGSEE青犀视频AI智能算法平台电动车入梯检测解决方案

一、方案背景 随着大众的出行要求逐渐提升&#xff0c;交通拥堵现象也随处可见&#xff0c;电动车出行&#xff0c;就成了大家的首选。随着电动车数量的激增&#xff0c;众多用户为了个人方便&#xff0c;大多在室内停放或充电&#xff0c;有的甚至停放在走道、楼梯间等公共区…

无涯教程-JavaScript - BITOR函数

描述 BITOR函数返回两个数字的按位"或"。 语法 BITOR (number1, number2)争论 Argument描述Required/OptionalNumber1Must be in decimal form and greater than or equal to 0.RequiredNumber2Must be in decimal form and greater than or equal to 0.Required…

基于Java+SpringBoot+Vue前后端分离青年公寓服务平台设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

vue基础知识七:SPA首屏加载速度慢的怎么解决?

一、什么是首屏加载 首屏时间&#xff08;First Contentful Paint&#xff09;&#xff0c;指的是浏览器从响应用户输入网址地址&#xff0c;到首屏内容渲染完成的时间&#xff0c;此时整个网页不一定要全部渲染完成&#xff0c;但需要展示当前视窗需要的内容 首屏加载可以说…

指针跃动(济南)客户运营服务中心上线了!

指针跃动&#xff08;济南&#xff09;客户运营服务中心上线了&#xff01; ——打通客户运营服务全链路—— 随着全国代驾业务需求的不断增长&#xff0c;“指针跃动”宣布&#xff1a;指针跃动&#xff08;济南&#xff09;客户运营服务中心上线了&#xff01; 以新的思维方式…

CMT:卷积与Transformers的高效结合

论文提出了一种基于卷积和VIT的混合网络&#xff0c;利用Transformers捕获远程依赖关系&#xff0c;利用cnn提取局部信息。构建了一系列模型cmt&#xff0c;它在准确性和效率方面有更好的权衡。 CMT:体系结构 CMT块由一个局部感知单元(LPU)、一个轻量级多头自注意模块(LMHSA)和…

end value has mixed support, consider using flex-end instead

这是因为 应该用flex-end,不应该用end 所以将所有的都改好之后&#xff0c;就不会再报这个错了

python3 修改nacos的yaml配置

一、安装nacos库 pip install nacos-sdk-python 二、代码如下 import nacos import yaml# 连接地址 NACOS_SERVER_ADDRESSES "192.168.xx.xx" NACOS_SERVER_PORT 替换为你的端口号&#xff0c;如8848# 命名空间 NACOS_NAMESPACE "your_namespace"# 账…

【网络层】网络基础 -- IP协议

引入IP协议头格式网段划分特殊的IP地址IP地址的数量限制 私有IP地址和公网IP地址分片与组装如何分片与组装&#xff1f; 引入 我们前面学习了传输层的相关知识&#xff0c;难道真的就是直接传送吗&#xff1f;当然不是&#xff0c;那TCP究竟做了什么&#xff1f;IP又扮演什么角…

RFID溯源驱动汽车座椅制造的智能时代

在今天的快速发展的制造业中&#xff0c;信息化和智能化已经成为不可或缺的部分。信息化和智能化能够极大地提高生产效率、减少浪费&#xff0c;降低成本&#xff0c;提升产品的质量。汽车座椅产线信息化和智能化是汽车座椅产线升级的重要方向&#xff0c;RFID技术方案在汽车座…

有了这个技术,再也不为水浸事件发愁啦!

在现代社会中&#xff0c;电力是我们生活和工作的不可或缺的一部分。电力供应的可靠性对于维持社会的正常运转至关重要。而变电站则是电力系统中的关键环节&#xff0c;它们起着将高压电转换为适用于分配的低压电的重要作用。然而&#xff0c;变电站也存在各种风险&#xff0c;…

学习Bootstrap 5的第六天

目录 信息警告框 警告框 实例 警告框链接 实例 关闭警告框 实例 警告框动画 实例 按钮 按钮样式 实例 按钮轮廓 实例 ​编辑按钮尺寸 实例 块级按钮 实例 实例 活动/禁用按钮 实例 加载器按钮 实例 扩展小知识 信息警告框 警告框 警告框是使用 .aler…

手写Spring:第15章-通过注解注入属性信息

文章目录 一、目标&#xff1a;通过注解注入属性信息二、设计&#xff1a;通过注解注入属性信息三、实现&#xff1a;通过注解注入属性信息3.1 工程结构3.2 自动扫描注入占位符配置和对象类图3.3 读取属性并填充到容器中3.3.1 定义解析字符串接口3.3.2 配置Bean工厂添加解析器3…

低能量电子束曝光技术

引言 直接蚀刻和剥离是两种比较流行的图案转移工艺。在直接蚀刻工艺中&#xff0c;首先使用光刻技术对聚合物抗蚀剂进行构图&#xff0c;然后通过干法蚀刻技术用抗蚀剂作为掩模将图案转移到衬底或子层上。 剥离过程中&#xff0c;膜(通常是金属)被涂覆在抗蚀剂结构上&#xf…

RoboTAP:由 Google DeepMind 开发的一款机器人操作系统

Google DeepMind 开发的一款机器人操作系统RoboTAP。该系统能够通过只需几分钟的示范&#xff0c;就能让机器人学会新的视觉运动任务。你只需要给它展示几次如何做某件事&#xff0c;比如拿起一个苹果放到果冻上&#xff0c;它就能学会这个动作。 工作原理 该系统能够通过视觉…

CMS-织梦[dede]-通用免登发布插件

CMS-织梦[dede]-通用免登发布插件 1. 织梦通用免登陆发布插件功能说明2. 织梦通用免登陆发布接口使用说明2-1 下载插件2-2 安装插件3 对接火车头等采集工具 3 爬虫【古诗文网】示例[可选]测试火车头入库模型 使用火车头&#xff0c;简数采集器&#xff0c;八爪鱼等文章采集工具…